Unit Testing of ExecutorService in Java With No Thread sleep
Unit testing concurrent code, especially code utilizing ExecutorService
, presents unique challenges due to its asynchronous nature. Traditional approaches often involve using Thread.sleep()
to wait for tasks to be completed, but this method is unreliable and can lead to flaky tests. In this article, we’ll explore alternative strategies to unit test ExecutorService without relying on Thread sleep method. This ensures reliable tests that do not depend on arbitrary sleep durations.
1. Understanding ExecutorService
ExecutorService
is a framework in Java for executing tasks asynchronously. It manages a pool of threads and allows you to submit tasks for concurrent execution. Testing code that uses ExecutorService
typically involves verifying that tasks are executed correctly and that the service behaves as expected under various conditions.
1.1 Challenges with Thread.sleep()
Using Thread.sleep()
in tests introduces several issues:
- Non-deterministic Tests: Timing-based tests can be unpredictable and may fail randomly due to variations in thread scheduling and execution speed.
- Slow Tests: Sleeping for a fixed duration can make tests unnecessarily slow, especially if tasks complete quickly or if longer delays are required to ensure completion.
2. Alternative Approaches to Unit Testing ExecutorService
To write reliable tests for ExecutorService
without Thread.sleep()
, consider the following approaches. First, we create a MyRunnable
class that implements the Runnable
interface and performs a long-running calculation (In this article, we are calculating the sum of a large range of numbers).
MyRunnable.java
public class MyRunnable implements Runnable { private final long start; private final long end; private long result; public MyRunnable(long start, long end) { this.start = start; this.end = end; } @Override public void run() { result = 0; for (long i = start; i <= end; i++) { result += i; } System.out.println("Calculation complete. Result: " + result); } public long getResult() { return result; } }
2.1 Use Future to Get the Result
To get the result of the task and ensure completion, we can use Future
.
FutureExampleTest.java
public class FutureExampleTest { @Test public void testFutureWithLongRunningCalculation() throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); // Create an instance of MyRunnable with a long-running calculation MyRunnable task = new MyRunnable(1, 1000000000L); // Submit the task to the executor and get a Future Future<?> future = executor.submit(task); // Wait for the task to complete and get the result future.get(); // Blocks until the task completes // Verify the result long expected = (1000000000L * (1000000000L + 1)) / 2; assertEquals(expected, task.getResult()); // Shutdown the executor executor.shutdown(); } }
In this example, we submit the MyRunnable
task to the executor and get a Future
object. The future.get()
method blocks until the task is completed, ensuring we can retrieve the result after completion.
2.2 Use CountDownLatch for Synchronization
To ensure the parent thread waits for the task to complete without using Thread.sleep()
, we can use CountDownLatch
.
ExecutorServiceExampleTest.java
public class ExecutorServiceExampleTest { @Test public void testExecutorServiceWithLongRunningCalculation() throws InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); CountDownLatch latch = new CountDownLatch(1); // Create a runnable with a long-running calculation MyRunnable task = new MyRunnable(1, 1000000000L) { @Override public void run() { super.run(); latch.countDown(); } }; // Submit the task to the executor executor.submit(task); // Wait for the task to complete assertTrue(latch.await(2, TimeUnit.MINUTES)); // Verify the result long expected = (1000000000L * (1000000000L + 1)) / 2; assertEquals(expected, task.getResult()); // Shutdown the executor executor.shutdown(); } }
This approach uses a CountDownLatch
to synchronize the completion of the task. First, we create a CountDownLatch
with a count of 1 and define an anonymous subclass of MyRunnable
that counts down the latch when the task completes.
Next, we submit this task to the executor and use latch.await()
to wait for the task to complete, verifying with assertTrue that the task finishes within the specified timeout. After the task is completed, we verify the result using assertEquals
. Finally, we shut down the executor.
2.3 Use Shutdown and Await Termination
To ensure the executor shuts down gracefully after the tasks complete, use shutdown
and awaitTermination
.
ShutDownExampleTest.java
public class ShutDownExampleTest { @Test public void testShutdownWithLongRunningCalculation() throws InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); // Create an instance of MyRunnable with a long-running calculation MyRunnable task = new MyRunnable(1, 1000000000L); // Submit the task to the executor executor.submit(task); // Shutdown the executor executor.shutdown(); // Wait for existing tasks to complete assertTrue(executor.awaitTermination(2, TimeUnit.MINUTES)); // Verify the result long expected = (1000000000L * (1000000000L + 1)) / 2; assertEquals(expected, task.getResult()); } }
In this approach, we ensure the executor shuts down gracefully by calling shutdown()
and then awaitTermination()
to wait for existing tasks to complete. If tasks do not complete within the specified timeout, we call shutdownNow()
to cancel currently executing tasks and wait again.
3. Conclusion
Unit testing concurrent code with ExecutorService
requires careful synchronization to ensure tests are reliable and deterministic. Avoiding Thread.sleep()
is essential to prevent flaky tests and improve test execution speed. In this article, we used synchronization aids like CountDownLatch
, Future
, and shutdown
with awaitTermination()
to handle concurrency effectively in our tests. These approaches provide more reliable alternatives to Thread.sleep()
for unit testing ExecutorService
-based code in Java.
4. Download the Source Code
This was an article on Unit Test of ExecutorService in Java Without Thread.sleep().
You can download the full source code of this example here: Java ExecutorService unit test no sleep