Managing Asynchronous Tests

It's recommended to avoid any asynchrony within the scope of the test. Unfortunately, this is not possible everywhere.


Testing asynchrony could be pretty tricky, not always is clear how long must a test wait for a result to be delivered - if it's too little the test fails event when the tested functionality works, if it's too long the test is just wasting time (so expensive especially in the commit stage).

So, how to deal with this problem?

Consider a simple test in JUnit:

@Test
public void shouldDeliverResult() {
    calculateResult();
    confirmResultWasDelivered();        
}

private void confirmResultWasDelivered() {
    if (!resultWasDelivered()) {
        fail("No result was delivered!");
    }
}

In this unit test we test a state of an object after calling the method calculateResult() with the method resultWasDelivered(). If the method calculateResult() is designed in the asynchronous manner, the test will fail almost in 100 % cases.

A naive solution is to add a timeout:

private void confirmResultWasDelivered() {
    sleep(TIMEOUT);
   
    if (!resultWasDelivered()) {
        fail("No result was delivered!");
    }
}
private void sleep(long milliseconds) {
    try {
        Thread.sleep(milliseconds);
    } catch (InterruptedException ignore) {
    }
}

But again, what is the right value of the timeout here?

We want to be sure that our test passes always when a correct result is delivered, so we set the timeout to a maximal waiting period. On the other hand in some cases the result could be delivered quickly in a small amout of time:

private void confirmResultWasDelivered() {
    long testStarted = System.currentTimeMillis();
    do {
        if (resultWasDelivered()) {
            return;
        }
        sleep(SMALL_AMOUT_OF_TIME);
   
    } while (TIMEOUT > System.currentTimeMillis() - testStarted);
   
    fail("No result was delivered!");
}

The last approach is still based on the pull princip and active waiting, but it's much more efficient as the previous one. 

We try to check if a result was delivered, if not we try it again after a small amout of time till the timeout exceeds.