I am very new to Akka and using Java to program my system.
Problem definition
- I have a TenantMonitor which when receives TenantMonitorMessage(), starts a new actor DiskMonitorActor.
- The DiskMonitorActor may fail for various reasons and may throw DiskException. The DiskMonitorActor has been Unit Tested.
What I need?
- I want to test behavior TenantMonitorActor, so that when DiskException happens, it takes correct action like stop(), resume() or any (depending upon what my application may need)
What I tried?
Based on the documentation, the closest I could perform is the section called Expecting Log Messages.
Where I need help?
- While I understand the expecting the correct error log is important, it just asserts first part, that exception is thrown and is logged correctly, but does not help in asserting that right strategy is called
Code?
TenantMonitorActor
public class TenantMonitorActor extends UntypedActor {
public static final String DISK_MONITOR = "diskMonitor";
private static final String assetsLocationKey = "tenant.assetsLocation";
private static final String schedulerKey = "monitoring.tenant.disk.schedule.seconds";
private static final String thresholdPercentKey = "monitoring.tenant.disk.threshold.percent";
private final LoggingAdapter logging = Logging.getLogger(getContext().system(), this);
private final Config config;
private TenantMonitorActor(final Config config) {
this.config = config;
}
private static final SupervisorStrategy strategy =
new OneForOneStrategy(1, Duration.create(1, TimeUnit.SECONDS),
new Function<Throwable, Directive>() {
public Directive apply(final Throwable param) throws Exception {
if (param instanceof DiskException) {
return stop();
}
return restart();
}
});
public static Props props(final Config config) {
return Props.create(new Creator<TenantMonitorActor>(){
public TenantMonitorActor create() throws Exception {
return new TenantMonitorActor(config);
}
});
}
#Override
public void onReceive(final Object message) throws Exception {
if (message instanceof TenantMonitorMessage) {
logging.info("Tenant Monitor Setup");
setupDiskMonitoring();
}
}
#Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
private void setupDiskMonitoring() {
final ActorRef diskMonitorActorRef = getDiskMonitorActorRef(config);
final FiniteDuration start = Duration.create(0, TimeUnit.SECONDS);
final FiniteDuration recurring = Duration.create(config.getInt(schedulerKey),
TimeUnit.SECONDS);
final ActorSystem system = getContext().system();
system.scheduler()
.schedule(start, recurring, diskMonitorActorRef,
new DiskMonitorMessage(), system.dispatcher(), null);
}
private ActorRef getDiskMonitorActorRef(final Config monitoringConf) {
final Props diskMonitorProps =
DiskMonitorActor.props(new File(monitoringConf.getString(assetsLocationKey)),
monitoringConf.getLong(thresholdPercentKey));
return getContext().actorOf(diskMonitorProps, DISK_MONITOR);
}
}
Test
#Test
public void testActorForNonExistentLocation() throws Exception {
final Map<String, String> configValues =
Collections.singletonMap("tenant.assetsLocation", "/non/existentLocation");
final Config config = mergeConfig(configValues);
new JavaTestKit(system) {{
assertEquals("system", system.name());
final Props props = TenantMonitorActor.props(config);
final ActorRef supervisor = system.actorOf(props, "supervisor");
new EventFilter<Void>(DiskException.class) {
#Override
protected Void run() {
supervisor.tell(new TenantMonitorMessage(), ActorRef.noSender());
return null;
}
}.from("akka://system/user/supervisor/diskMonitor").occurrences(1).exec();
}};
}
UPDATE
The best I could write is to make sure that the DiskMonitor is stopped once the exception occurs
#Test
public void testSupervisorForFailure() {
new JavaTestKit(system) {{
final Map<String, String> configValues =
Collections.singletonMap("tenant.assetsLocation", "/non/existentLocation");
final Config config = mergeConfig(configValues);
final TestActorRef<TenantMonitorActor> tenantTestActorRef = getTenantMonitorActor(config);
final ActorRef diskMonitorRef = tenantTestActorRef.underlyingActor().getContext()
.getChild(TenantMonitorActor.DISK_MONITOR);
final TestProbe testProbeDiskMonitor = new TestProbe(system);
testProbeDiskMonitor.watch(diskMonitorRef);
tenantTestActorRef.tell(new TenantMonitorMessage(), getRef());
testProbeDiskMonitor.expectMsgClass(Terminated.class);
}};
}
Are there better ways?
I have the feeling that testing supervisor strategy is some sort of grey area -- it is up to personal opinion where we start testing Akka itself, instead of one's understanding of how the framework works. Testing validation of entities in ORM frameworks strikes me as a similar problem. We don't want to test whether email validation logic is correct (e.g. in Hibernate), but rather if our rule is correctly declared.
Following this logic, I would write the test as follows:
final TestActorRef<TenantMonitorActor> tenantTestActorRef =
getTenantMonitorActor(config);
SupervisorStrategy.Directive directive = tenantTestActorRef.underlyingActor()
.supervisorStrategy().decider().apply(new DiskException());
assertEquals(SupervisorStrategy.stop(), directive);
Related
I have a project that uses Spring Cloud Streams - RabbitMQ to exchange messages within micro-services. One thing that is critical for my project is that I must not lose any message.
In order to minimize failures, I planned the following:
Use the default retry method for messages in queue
Configure dead-letter queue to put messages again on queue after some time
To avoid an infinite loop, allow only a few times (let's say, 5) a message could be republished from dead-letter queue to regular messaging queue.
The first two items I believe I could make it using the configuration below:
#dlx/dlq setup - retry dead letter 5 minutes later (300000ms later)
spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true
spring.cloud.stream.rabbit.bindings.input.consumer.republish-to-dlq=true
spring.cloud.stream.rabbit.bindings.input.consumer.dlq-ttl=300000
spring.cloud.stream.rabbit.bindings.input.consumer.dlq-dead-letter-exchange=
#input
spring.cloud.stream.bindings.myInput.destination=my-queue
spring.cloud.stream.bindings.myInput.group=my-group
However, I could not find searching on this reference guide how to do what I want (mostly, how to configure a maximum number of republish from dead-letter queue). I'm not completely sure I'm on the right path - maybe I should manually create a second queue and code what I want, and leave dead-letter only to messages that completely failed (which I must check regularly and handle manually, since my system should not lose any messages)...
I'm new to these frameworks, and I would like your help to configure mine...
This documentation for the rabbit binder shows how to publish a dead-letter to some parking-lot queue after some number of retries have failed.
#SpringBootApplication
public class ReRouteDlqApplication {
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
private static final String X_RETRIES_HEADER = "x-retries";
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
System.out.println("Hit enter to terminate");
System.in.read();
context.close();
}
#Autowired
private RabbitTemplate rabbitTemplate;
#RabbitListener(queues = DLQ)
public void rePublish(Message failedMessage) {
Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER);
if (retriesHeader == null) {
retriesHeader = Integer.valueOf(0);
}
if (retriesHeader < 3) {
failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1);
this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage);
}
else {
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
}
}
#Bean
public Queue parkingLot() {
return new Queue(PARKING_LOT);
}
}
The second example shows how to use the delayed exchange plugin to delay between retries.
#SpringBootApplication
public class ReRouteDlqApplication {
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
private static final String X_RETRIES_HEADER = "x-retries";
private static final String DELAY_EXCHANGE = "dlqReRouter";
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
System.out.println("Hit enter to terminate");
System.in.read();
context.close();
}
#Autowired
private RabbitTemplate rabbitTemplate;
#RabbitListener(queues = DLQ)
public void rePublish(Message failedMessage) {
Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
if (retriesHeader == null) {
retriesHeader = Integer.valueOf(0);
}
if (retriesHeader < 3) {
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
headers.put("x-delay", 5000 * retriesHeader);
this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage);
}
else {
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
}
}
#Bean
public DirectExchange delayExchange() {
DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE);
exchange.setDelayed(true);
return exchange;
}
#Bean
public Binding bindOriginalToDelay() {
return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE);
}
#Bean
public Queue parkingLot() {
return new Queue(PARKING_LOT);
}
}
I have the code below:
private SaveTransactionClient mockedTransactionClient;
private static Publisher publisher;
private static MyDTO mDTO1;
private static MyDTO mDTO2;
private static MyDTO mDTO3;
#BeforeClass
public void setUp() throws IOException {
TransactionResponse successResponse = new TransactionResponse();
successResponse.setDateRequest("2016-04-27 18:47:50");
successResponse.setResponse("OK");
successResponse.setTransactionNumber("1");
TransactionResponse failedResponse = new TransactionResponse();
failedResponse.setDateRequest("2016-04-27 18:47:50");
failedResponse.setResponse("Dublicate Transaction Error");
failedResponse.setTransactionNumber("1");
mDTO1 = new MyDTO(1, LocalDateTime.now(), 0);
mDTO2 = new MyDTO(2, LocalDateTime.now(), 0);
mDTO3 = new MyDTO(3, LocalDateTime.now(), 0);
mockedTransactionClient = mock(SaveTransactionClient.class);
when(mockedTransactionClient.sendTransactionRequest(mDTO1)).thenReturn(successResponse);
when(mockedTransactionClient.sendTransactionRequest(mDTO2)).thenReturn(failedResponse);
when(mockedTransactionClient.sendTransactionRequest(mDTO3)).thenThrow(new IOException());
when(mockedTransactionClient.sendTransactionRequest(any(MDTO.class))).thenThrow(new IOException());
publisher = new publisherImpl(mockedTransactionClient);
}
The actual tests are
#Test
public void TestOnlyExceptionalPublishing() {
BlockingQueue<MDTO> mDTOs = new LinkedBlockingQueue<>(Arrays.asList(mDTO3));
assertEquals(mDTOs.size(), 1);
List<MDTO> successful = publisher.publish(wDTOs);
assertEquals(successful.size(), 0);
}
#Test
public void TestOneSuccessContainsExceptionalPublishing() {
BlockingQueue<MDTO> mDTOs = new LinkedBlockingQueue<>(Arrays.asList(mDTO3,mDTO1, mDTO2));
assertEquals(mDTOs.size(), 3);
List<MDTO> successful = publisher.publish(mDTOs);
assertEquals(successful.size(), 1);
}
Now the MDTO is immutable and the way the publisher.publish(mDTO) works is that on Exception increments a "retries" counter in MDTO and retry up to 3 times. When the counter is incremented a new MDTO is generated which, with the response not being stubbed by Mockito, creates a problem for me. I added the any but this makes the other tests fail since it throws for all and not for any OTHER except for the objects I have already supplied.
Is there any anyOther type of way to do this in Mockito?
I think (if I did understand correctly) that you are looking for doAnswer/thenAnswer:
when(mockedTransactionClient.sendTransactionRequest(any(MyDTO.class)).thenAnswer(new Answer<TransactionResponse>() {
#Override
public TransactionResponse answer(final InvocationOnMock invocation) {
final MyDTO arg = invocation.getArgumentAt(0, MyDTO.class)
// do stuff here based on arg
return someTransactionResponse; // or throw some exception
}
});
I'm working my way through the example code of some Storm topologies and bolts, but I'm running into something weird. My goal is to set up Kafka with Storm, so that Storm can process the messages available on the Kafka bus. I have the following bolt defined:
public class ReportBolt extends BaseRichBolt {
private static final long serialVersionUID = 6102304822420418016L;
private Map<String, Long> counts;
private OutputCollector collector;
#Override #SuppressWarnings("rawtypes")
public void prepare(Map stormConf, TopologyContext context, OutputCollector outCollector) {
collector = outCollector;
counts = new HashMap<String, Long>();
}
#Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
// terminal bolt = does not emit anything
}
#Override
public void execute(Tuple tuple) {
System.out.println("HELLO " + tuple);
}
#Override
public void cleanup() {
System.out.println("HELLO FINAL");
}
}
In essence, it should just output each Kafka message; and when the cleanup function is called, a different message should appear.
I have looked at the worker logs, and I find the final message (i.e. "HELLO FINAL"), but the Kafka messages with "HELLO" are nowhere to be found. As far as I can tell this should be a simple printer bolt, but I can't see where I'm going wrong. The workers logs indicate I am connected to the Kafka bus (it fetches the offset etc.).
In short, why are my println's not showing up in the worker logs?
EDIT
public class AckedTopology {
private static final String SPOUT_ID = "monitoring_test_spout";
private static final String REPORT_BOLT_ID = "acking-report-bolt";
private static final String TOPOLOGY_NAME = "monitoring-topology";
public static void main(String[] args) throws Exception {
int numSpoutExecutors = 1;
KafkaSpout kspout = buildKafkaSpout();
ReportBolt reportBolt = new ReportBolt();
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(SPOUT_ID, kspout, numSpoutExecutors);
builder.setBolt(REPORT_BOLT_ID, reportBolt);
Config cfg = new Config();
StormSubmitter.submitTopology(TOPOLOGY_NAME, cfg, builder.createTopology());
}
private static KafkaSpout buildKafkaSpout() {
String zkHostPort = "URL";
String topic = "TOPIC";
String zkRoot = "/brokers";
String zkSpoutId = "monitoring_test_spout_id";
ZkHosts zkHosts = new ZkHosts(zkHostPort);
SpoutConfig spoutCfg = new SpoutConfig(zkHosts, topic, zkRoot, zkSpoutId);
KafkaSpout kafkaSpout = new KafkaSpout(spoutCfg);
return kafkaSpout;
}
}
Your bolt is not chained with the spout. You need to use storm's grouping in order to do that .. Use something like this
builder.setBolt(REPORT_BOLT_ID, reportBolt).shuffleGrouping(SPOUT_ID);
The setBolt typically returns a InputDeclarer object. In your case by specifying shuffleGrouping(SPOUT_ID) you are telling storm that you are interested in consuming all the tuples emitted by component having id REPORT_BOLT_ID.
Read more on stream groupings and choose the one based on your need.
I use the following enum type:
enum Status {OK,TIMEOUT,EXCEPTION}
But now I want to store what exactly the Exception is. Unfortunately you cannot instantiate an enum type. What is the best way to make something like the following possible?
switch(status)
{
case(OK) {System.out.println("Everything OK!");break;}
case(TIMEOUT) {System.out.println("Timeout :-(");break;}
case(EXCEPTION) {System.out.println("We have an exception: "+status.exception);break;}
}
My ideas
Class with singletons
class Status
{
final Exception e;
public final Status OK = new Status(null);
public final Status TIMEOUT = new Status(null);
public Status(Exception e) {this.e=e;}
}
Then I could do:
if(status==Status.OK) {System.out.println("Everything OK!");}
else if(status==Status.TIMEOUT) {System.out.println("Timeout :-(");}
else {System.out.println("We have an exception: "+status.exception);}
2. Several Classes
class Status {}
class StatusOK extends Status {}
class StatusTimeout extends Status {}
class StatusException extends Status
{
final Exception e;
public StatusException(Exception e) {this.e=e;}
}
Then I would need a bunch of "instanceOf"-statements.
P.S.: OK it seems that I didn't explain it clearly enough. In my program I answer requests and I store the status of the processing of those requests:
Map<Request,Status> request2Status;
Thus I cannot use something like Status.getMessage(exception); because at that position in my code I do not know which exception it was. That why I want to save it inside the status.
Chosen solution
private static class LearnStatus implements Serializable
{
private static final long serialVersionUID = 1L;
public static final LearnStatus OK = new LearnStatus(null);
public static final LearnStatus TIMEOUT = new LearnStatus(null);
public static final LearnStatus NO_TEMPLATE_FOUND = new LearnStatus(null);
public static final LearnStatus QUERY_RESULT_EMPTY = new LearnStatus(null);
public static final LearnStatus NO_QUERY_LEARNED = new LearnStatus(null);
public final Exception exception;
private LearnStatus(Exception exception) {this.exception = exception; }
public static LearnStatus exceptionStatus(Exception cause)
{
if (cause == null) throw new NullPointerException();
return new LearnStatus(cause);
}
#Override public String toString()
{
if(this==OK) {return "OK";}
if(this==TIMEOUT) {return "timeout";}
if(this==NO_TEMPLATE_FOUND) {return "no template found";}
if(this==QUERY_RESULT_EMPTY) {return "query result empty";}
if(this==NO_QUERY_LEARNED) {return "no query learned";}
return "<summary>Exception: <details>"+exception.getLocalizedMessage()+"</details></summary>";
}
}
Problems with that
If I serialize an object with Status.OK in it, after deserialization if(status==Status.OK) does not work anymore.
New solution
I now included an enum type within the class. What do you think about it?
private static class LearnStatus implements Serializable
{
public enum Type {OK, TIMEOUT, NO_TEMPLATE_FOUND,QUERY_RESULT_EMPTY,NO_QUERY_LEARNED,EXCEPTION}
public final Type type;
private static final long serialVersionUID = 1L;
public static final LearnStatus OK = new LearnStatus(Type.OK,null);
public static final LearnStatus TIMEOUT = new LearnStatus(Type.TIMEOUT,null);
public static final LearnStatus NO_TEMPLATE_FOUND = new LearnStatus(Type.NO_TEMPLATE_FOUND,null);
public static final LearnStatus QUERY_RESULT_EMPTY = new LearnStatus(Type.QUERY_RESULT_EMPTY,null);
public static final LearnStatus NO_QUERY_LEARNED = new LearnStatus(Type.NO_QUERY_LEARNED,null);
public final Exception exception;
private LearnStatus(Type type, Exception exception) {this.type=type;this.exception = exception;}
public static LearnStatus exceptionStatus(Exception cause)
{
if (cause == null) throw new NullPointerException();
return new LearnStatus(Type.EXCEPTION,cause);
}
#Override public String toString()
{
switch(type)
{
case OK: return "OK";
case TIMEOUT: return "timeout";
case NO_TEMPLATE_FOUND: return "no template found";
case QUERY_RESULT_EMPTY:return "query result empty";
case NO_QUERY_LEARNED: return "no query learned";
case EXCEPTION: return "<summary>Exception: <details>"+exception.getLocalizedMessage()+"</details></summary>";
default: throw new RuntimeException("switch type not handled");
}
}
}
I would use an Exception unless everything is OK.
Like
System.out.println("Everything OK!");
} catch(TimeoutException te) {
System.out.println("Timeout :-(")
} catch(Exception e) {
System.out.println("We have an exception: " + e);
}
I don't see any need to use an enum when Exceptions are designed to do this sort of thing.
Adding yet another layer on top of the layer between you and the original exception you can do this.
interface Status {
String getMessage();
}
enum Statuses implements Status {
OK("Everything OK"), TIMEOUT("Timeout :-(");
private final String message;
private Statuses(String message) { this.message = message; }
String getMessage() { return message; }
}
class ExceptionStatus implement Status {
private final String message;
String getMessage() { return "Exception: " + message; }
}
// to print the message
System.out.println(status.getMessage());
There are several approaches to this, but all of them depend that you don't use Enums or that you don't use them exclusively. Keep in mind that an enum is basically a class that only has well-defined singletons as value.
One possible refactoring of this is to use a normal class with well-defined singletons instead of enums:
class Status implements Serializable {
// for serialization
private enum InternalStatus {
OK, TIMEOUT, EXCEPTION
}
public static final Status OK = new Status(null, InternalStatus.OK);
public static final Status TIMEOUT = new Status(null, InternalStatus.TIMEOUT);
private final Exception exception;
private final InternalStatus internalStatus;
private Status(Exception exception, InternalStatus internalStatus) {
this.exception = exception;
this.internalStatus = internalStatus;
}
public Exception getException() {
return exception;
}
public static Status exceptionStatus(Exception cause) {
if (cause == null) throw new NullPointerException();
return new Status(cause, InternalStatus.EXCEPTION);
}
// deserialization logic handling OK and TIMEOUT being singletons
private final Object readResolve() {
switch (internalStatus) {
case InternalStatus.OK:
return OK;
case InternalStatus.TIMEOUT:
return TIMEOUT;
default:
return this;
}
}
}
You can now check for status == Status.OK and status == Status.TIMEOUT. If your status variable is neither OK nor TIMEOUT, it must be caused by an exception, which you can retrieve via getException.
As a downside, you lose the switch functionality and must check via if.
I'm wondering how to go about checking that a method returns a container encapsulating some collection which is the aggregate of multiple other containers returned by mock objects. That is, it contains all the elements of the individual containers. I have some tests elsewhere that check the container 'works' (add/addAll/etc), so I know that works, but I'm not sure how go about with the test below 'createsRoadUsersAccordingToAllAddedCreators'.
I have a RoadUserCreationDaemon class which I call create upon which returns a RoadUserContainer according to added RoadUserCreator's. A simplified version:
public class RoadUserCreationDaemon {
private SimulationManager simulationManager;
private List<RoadUserCreator> roadUserCreators;
public RoadUserCreationDaemon(SimulationManager simulationManager) {
this.simulationManager = simulationManager;
roadUserCreators = new ArrayList<RoadUserCreator>();
}
public void addRoadUserCreator(RoadUserCreator roadUserCreator) {
roadUserCreators.add(roadUserCreator);
}
public RoadUserContainer createRoadUsers() {
RoadUserContainer roadUsers = new RoadUserContainerImpl();
for (RoadUserCreator creator : roadUserCreators) {
roadUsers.addAll(createRoadUsers(creator));
}
return roadUsers;
}
public RoadUserContainer createRoadUsers(
RoadUserCreator roadUserCreator) {
return roadUserCreator.create();
}
}
I started by writing a test (JUnit4 / JMock2.5.1) for createRoadUsers which returns a RoadUserContainer with a supplied creator. Then I started writing a test for a non-parameterised createRoadUsers to see if it returns a container with all the elements of the individual containers returned by the creators:
#RunWith(JMock.class)
public class TestRoadUserCreationDaemon {
Mockery context = new JUnit4Mockery();
private RoadUserCreationDaemon daemon;
private RoadUserCreator roadUserCreator;
private SimulationManager simulationManager;
private RoadUserContainer createdRoadUsers;
#Before
public void setUp() {
simulationManager = context.mock(SimulationManager.class);
daemon = new RoadUserCreationDaemon(simulationManager);
roadUserCreator = context.mock(RoadUserCreator.class);
createdRoadUsers = context.mock(RoadUserContainer.class);
}
#Test
public void createsRoadUsersAccordingToAllAddedCreators() throws Exception {
final RoadUserCreator anotherRoadUserCreator = context.mock(RoadUserCreator.class, "anotherRUC");
final RoadUserContainer moreCreatedRoadUsers = context.mock(RoadUserContainer.class, "moreCRU");
context.checking(new Expectations() {{
oneOf (roadUserCreator).create(); will(returnValue(createdRoadUsers));
oneOf (anotherRoadUserCreator).create(); will(returnValue(moreCreatedRoadUsers));
oneOf (createdRoadUsers).roadUsersAsList();
oneOf (moreCreatedRoadUsers).roadUsersAsList();
}});
daemon.addRoadUserCreator(roadUserCreator);
daemon.addRoadUserCreator(anotherRoadUserCreator);
daemon.createRoadUsers();
//how to easily check that the two lists are equivilant - have same items, but not the same object?
//assertEquals(createdRoadUsers, daemon.createRoadUsers() );
}
#Test
public void createsRoadUsersAccordingToCreator() throws Exception {
context.checking(new Expectations() {{
oneOf (roadUserCreator).create(); will(returnValue(createdRoadUsers));
}});
assertEquals(createdRoadUsers, daemon.createRoadUsers(roadUserCreator));
}
}
As the comment says...I'm not sure how to proceed in a non-ugly way.
The 'RoadUserContainer' interface:
public interface RoadUserContainer extends Iterable<RoadUser> {
public void add(RoadUser roadUser);
public Iterator<RoadUser> iterator();
public void addAll(RoadUserContainer createRoadUsers);
public List<RoadUser> roadUsersAsList();
public boolean equals(RoadUserContainer otherContainer);
...
}
I am new to TDD and mocking, and this is my first Java project for >6 years, so feel free to comment on ancillary aesthetics!
I would probably initially use real containers and mock the other objects. Then use hamcrest to interrogate the resulting object.
The test I would want to create would look something like this:
final RoadUser roadUser0 = context.mock(RoadUser.class, "roadUser0");
final RoadUser roadUser1 = context.mock(RoadUser.class, "roadUser1");
final RoadUser roadUser2 = context.mock(RoadUser.class, "roadUser2");
final RoadUserCreator roadUserCreator0 = context.mock(RoadUserCreator.class, "roadUserCreator0");
final RoadUserCreator roadUserCreator1 = context.mock(RoadUserCreator.class, "roadUserCreator1");
final RoadUserCreationDaemon daemon = new RoadUserCreationDaemon(null);
daemon.addRoadUserCreator(roadUserCreator0);
daemon.addRoadUserCreator(roadUserCreator1);
context.checking(new Expectations() {{
oneOf(roadUserCreator0).create(); will(returnValue(roadUsers(roadUser0, roadUser1)));
oneOf(roadUserCreator1).create(); will(returnValue(roadUsers(roadUser2)));
}});
assertThat(daemon.createRoadUsers(), contains(roadUser0, roadUser1, roadUser2));
you will need these imports from hamcrest:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
If order is not important you could use containsInAnyOrder instead of contains
you would also need to create the utility method "roadUsers"
public static RoadUserContainer roadUsers(final RoadUser... roadUsers)
{
return new RoadUserContainerImpl(roadUsers);
}
An alternative design would be to change the interface of the RoadUserCreationDaemon
public void createRoadUsers(final RoadUserContainer roadUsers) {
for (final RoadUserCreator roadUserCreator : roadUserCreators) {
roadUsers.addAll(roadUserCreator.create());
}
}
Then you could write the tests like this:
final RoadUserContainer roadUserContainer0 = context.mock(RoadUserContainer.class, "roadUserContainer0");
final RoadUserContainer roadUserContainer1 = context.mock(RoadUserContainer.class, "roadUserContainer1");
final RoadUserContainer resultRoadUserContainer = context.mock(RoadUserContainer.class, "resultRoadUserContainer");
final RoadUserCreator roadUserCreator0 = context.mock(RoadUserCreator.class, "roadUserCreator0");
final RoadUserCreator roadUserCreator1 = context.mock(RoadUserCreator.class, "roadUserCreator1");
final RoadUserCreationDaemon daemon = new RoadUserCreationDaemon(null);
daemon.addRoadUserCreator(roadUserCreator0);
daemon.addRoadUserCreator(roadUserCreator1);
context.checking(new Expectations() {
{
oneOf(roadUserCreator0).create();
will(returnValue(roadUserContainer0));
oneOf(roadUserCreator1).create();
will(returnValue(roadUserContainer1));
oneOf(resultRoadUserContainer).addAll(roadUserContainer0);
oneOf(resultRoadUserContainer).addAll(roadUserContainer1);
}
});
daemon.createRoadUsers(resultRoadUserContainer);
If the order of the calls to "addAll" is important you can use a jmock sequence
I think I would mock the Creator but have it return real Containers. The idea of the test is to make sure that the Daemon invoked all of the creator's create methods, right? So your test condition would look like
RoadUserContainer result = daemon.createRoadUsers();
// Check that the result contains both creator's users
Assert.assertEquals(createdRoadUsers.size() + moreCreatedRoadUsers.size(), result.size());
for (RoadUser user : createdRoadUsers)
Assert.assertTrue(result.contains(user));
for (RoadUser user : moreCreatedRoadUsers)
Assert.assertTrue(result.contains(user));