Let's imagine I have the following method which should be tested:
@Autowired
private RoutingService routingservice;
public void methodToBeTested() {
Object objectToRoute = initializeObjectToRoute();
if (someConditions) {
routingService.routeInOneWay(objectToRoute);
} else {
routingService.routeInAnotherWay(objectToRoute);
}
}
In this case RoutingService
is running in the separate thread, thus in it's constructor we have the following:
Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();
The problem is that RoutingService
changes the state of objectToRoute
and this is exactly what I want to check, but this doesn't happen straight away thus the test fails. However, if I add Thread.sleep()
then it works, but this is bad practice as I know.
How can I avoid Thread.sleep()
in this case?
If you are testing methodToBeTested
, you should simply mock routingservice
. You shouldn't be testing any methods that methodToBeTested
calls. However, it sounds like you want to test the RoutingService
(you said "The problem is that RoutingService
changes the state of objectToRoute
and this is exactly what I want to check"). To test RoutingService
methods, you should write separate unit tests for those methods.
You could mock objectToRoute
to set the value of a CompletableFuture
and then call get
on that in your assertion. This will wait until the value is set before continuing. Then set a timeout @Test(timeout=5000)
in case the value is never set.
This has the advantage that the test won't wait longer than necessary and it's harder to fail because of too short a time because you can make the timeout much larger than normal.
I suggest awaitility for synchronizing asynchronous tests. For example, assume you have an result object that get set after some thread operations and you want to test it. You write statement like this:
await()
.atMost(100, TimeUnit.SECONDS)
.untilAsserted(() -> assertNotNull(resultObject.getResult()));
It waits maximum 100 seconds, or until the assertion is satisfied. For instance, if getResult() returns something that is not null in between 0-100 seconds, the execution continues unlike Thread.sleep which holds the execution for given time no matter result is there or not.
It depends. As Ben Green said in his comment, sleep is dangerous in tests because it can hide a race condition. You know if the overall design contains such a race condition, where the service could be used before the route is ready. It it does, you should fix it in code, for example by testing a ready condition, and test it the same in your test class.
If you know that it cannot happen, you should document it in you main code and in your test class. That would then be a perfect justification for a sleep.
(I assume that this is for an integration test - for unit tests mocks should be enough as you were said in other answers)
Instead of avoiding Thread.sleep(), You can pass value as zero in Junit Test Case.
Use Object.wait()
We have worked with some asynchronous processes that obtain files from directories or .ZIP archives. We use the content of the files somewhere else while the asynchronous one keeps on reading.
The way we came around to test them was to wait()
on the communication object --in our case it was a Queue--, so the asynchronous process would queue.notifyAll()
whenever there was a new file ready to be used and kept working. On the other end the consumer would process one item at a time until the queue was empty and then queue.wait()
to wait for more. We do use a special object passed through the queue to indicate that there are no more items to process.
In your case I suppose the object you want to check, objectToRoute,
is not really created inside the method you want to test. You could wait()
on it inside the test, and notifyAll()
on it inside the method you want to test, methodToBeTested.
I know this would introduce the extra line of code in your production code base, but with nobody waiting on it, it should result harmless. It would end up something like:
public void methodToBeTested(Object objectToRoute) {
if (someConditions) {
routingService.routeInOneWay(objectToRoute);
} else {
routingService.routeInAnotherWay(objectToRoute);
}
synchronized(objectToRoute) {
objectToRoute.notifyAll();
}
}
And in your test class there will be something like:
@Test
public void testMethodToBeTested() throws InterruptedException {
Object objectToRoute = initializeObjectToRoute();
methodToBeTested(objectToRoute);
synchronized (objectToRoute) {
objectToRoute.wait();
}
verifyConditionsAfterRouting(objectToRoute);
}
I do know that in this simple example does not make too much sense, since the sample system is not multithreaded. I assume the multithread twist is added in the routeInOneWay
and routeInAnotherWay
methods; thus, those are the ones to call notifyAll()
method.
As sample code to point the direction of our solution, here are some code fragments.
In the asynchronous worker side, or producer:
while(files.hasNext(){
queue.add(files.next());
synchronized (outputQueue) {
queue.notifyAll()
}
}
And in the consumer side:
while(!finished){
while(!queue.isEmpty()){
nextFile = queue.poll();
if (nextFile.equals(NO_MORE_FILES_SIGNAL)) {
finished = true;
break;
}
doYourThingWith(nextFile);
}
if (!finished) {
synchronized (outputQueue) {
outputQueue.wait();
}
}
}