Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

An ExecutorService object runs tasks in the background. Unit testing a task that runs on another thread is challenging. The parent thread must wait for the task to end before asserting its results.

Furthermore, a solution to this problem is to use Thread.sleep() method. This method blocks the parent thread for a defined range of time. Nevertheless, if the task exceeds the time set on sleep(), the unit test finishes before the task and fails.

In this tutorial, we’ll learn how to unit test an ExecutorService instance without using the Thread.sleep() method.

2. Creating a Runnable Object

Before getting into tests, let’s create a class that implements the Runnable interface:

public class MyRunnable implements Runnable {
    Long result;

    public Long getResult() {
        return result;
    }

    public void setResult(Long result) {
        this.result = result;
    }

    @Override
    public void run() {
        result = sum();
    }

    private Long sum() {
        Long result = 0L;
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            result += i;
        }
        return result;
    }
}

The MyRunnable class performs a calculation that takes a lot of time. The calculated sum is then set to the result member field. So, this will be the task we’ll submit to the executor.

3. The Problem

Typically, an ExecutorService object runs a task in a background thread. Tasks implement the Callable or Runnable interface.

If the parent thread isn’t waiting, it terminates before the task completion. So, the test always fails.

Let’s create a unit test to verify the problem:

ExecutorService executorService = Executors.newSingleThreadExecutor();
MyRunnable r = new MyRunnable();
executorService.submit(r);
assertNull(r.getResult());

We first created an ExecutorService instance with a single thread in this test. Then, we created and submitted a task. In the end, we asserted the value of the result field.

At runtime, the assertion runs before the end of the task. So, getResult() returns null.

4. Using the Future Class

The Future class denotes the result of the background task. Also, it can block the parent thread until the task finishes.

Let’s modify our test to use the Future object returned by the submit() method:

Future<?> future = executorService.submit(r);
future.get();
assertEquals(2305843005992468481L, r.getResult());

Here, the get() method of the Future instance blocks until the task ends.

Also, get() may return a value when the task is an instance of CallableIf the task is an instance of Runnable, get() always returns null.

Running the test now takes longer than previously. This is a sign that the parent thread is waiting for the task to finish. Finally, the test is successful.

5. Shutdown and Wait

Another option is to use the shutdown() and awaitTermination() methods of the ExecutorService class.

The shutdown() method shuts down the executor. The executor doesn’t accept any new tasks. Existing tasks aren’t killed. However, it doesn’t wait for them to end.

On the other hand, we can use the awaitTermination() method to block until all submitted tasks end. In addition, we should set a blocking timeout on the method. Going past the timeout means that the blocking ends.

Let’s alter the previous test to use these two methods:

executorService.shutdown();
executorService.awaitTermination(10000, TimeUnit.SECONDS);
assertEquals(2305843005992468481L, r.getResult());

As can be seen, we shut down the executor after we submitted the task. Next, we call awaitTermination() to block the thread until the task finishes.

Also, we set a maximum timeout of 10000 seconds. Therefore, if the task runs more than 10000 seconds, the method unblocks, even if the task hasn’t ended. In other words, if we set a small timeout value, awaitTermination() prematurely unblocks like Thread.sleep() does.

Indeed, the test is successful when we run it.

6. Using a ThreadPoolExecutor

Another option is to create an ExecutorService object that accepts a defined number of jobs and blocks until they finish.

A simple way is to extend the ThreadPoolExecutor class:

public class MyThreadPoolExecutor extends ThreadPoolExecutor {
    CountDownLatch doneSignal = null;

    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
        int jobsNumberToWaitFor) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        doneSignal = new CountDownLatch(jobsNumberToWaitFor);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        doneSignal.countDown();
    }

    public void waitDone() throws InterruptedException {
        doneSignal.await();
    }
}

Here, we create the MyThreadPoolExecutor class that extends ThreadPoolExecutor. In its constructor, we add the jobsNumberToWaitFor parameter, the number of jobs we plan to submit.

Furthermore, the class uses the doneSignal field, an instance of the CountDownLatch class. The doneSignal field is initialized in the constructor with the number of jobs to wait for. Next, we override the afterExecute() method to decrement doneSignal by one. The afterExecute() method is invoked when a job ends.

Finally, we have the waitDone() method that uses doneSignal to block until all jobs end.

In addition, we can test the above implementation with a unit test:

@Test
void whenUsingThreadPoolExecutor_thenTestSucceeds() throws InterruptedException {
    MyThreadPoolExecutor threadPoolExecutor = new MyThreadPoolExecutor(3, 6, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), 20);
    List<MyRunnable> runnables = new ArrayList<MyRunnable>();
    for (int i = 0; i < 20; i++) {
        MyRunnable r = new MyRunnable();
        runnables.add(r);
        threadPoolExecutor.submit(r);
    }
    threadPoolExecutor.waitDone();
    for (int i = 0; i < 20; i++) {
        assertEquals(2305843005992468481L, runnables.get(i).result);
    }
}

In this unit test, we submit 20 jobs to the executor. Immediately after that, we call the waitDone() method that blocks until 20 jobs finish. Finally, we assert the result of each job.

7. Conclusion

In this article, we learned how to unit test an ExecutorService instance without using the Thread.sleep() method. That is to say, we looked at three methods:

  • obtaining a Future object and invoking the get() method
  • shutting down the executor and waiting for running tasks to finish
  • creating a custom ExecutorService

As always, the full source code of our examples can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Subscribe
Notify of
guest
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments