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 Callable. If 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

In the previous sections, we used the Executors class to create new executors. Alternatively, we may create an ExecutorService class, like ThreadPoolExecutor, by directly calling its constructor.

The benefit is that we get access to extra methods that aren’t available in the ExecutorService interface:

ThreadPoolExecutor threadPoolExecutor = 
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
MyRunnable r = new MyRunnable();
threadPoolExecutor.submit(r);
while (threadPoolExecutor.getCompletedTaskCount() < 1) {
}
assertEquals(2305843005992468481L, r.getResult());

In this example, we created a new ThreadPoolExecutor instance with one worker. Similarly to the previous examples, we create a new Runnable and submit it for execution.

The ThreadPoolExecutor class provides the getCompletedTaskCount() method. This method returns an approximate number of the tasks completed. Each worker thread records the number of tasks completed, and the getCompletedTaskCount() method sums the completed tasks of all workers.

Furthermore, we used this method in the condition statement of the while loop. As a result, the while loop runs until the task we submitted is completed and added to the task count.

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
  • using the ThreadPoolExecutor class

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