How to unit test a code snippet running inside executor service, instead waiting on Thread.sleep(time)
Asked Answered
I

11

32

How to unit test a code that is running in executor service? In my situation,

public void test() {
    Runnable R = new Runnable() {
        @Override
        public void run() {
            executeTask1();
            executeTask2();
        }
    };

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(R);
}

When I am unit testing, I would like to make some validations that method executes.

I am executing this in an executor service, as it makes some network operations.

In my unit testing, I had to wait until this method finishes execution. Is there a better way I can do this, instead of waiting for Thread.sleep(500).

Unit testing code snippet:

@Test
public void testingTask() {
    mTestObject.test();
    final long threadSleepTime = 10000L;
    Thread.sleep(threadSleepTime);
    verify(abc, times(2))
            .acquireClient(a, b, c);
    verify(abd, times(1)).addCallback(callback);
}

Note: I am passing an executor service object into this constructor class. I would like to know if there is a good way of testing instead of waiting for sleep time.

Incomparable answered 24/9, 2017 at 18:54 Comment(0)
R
36

You could also implement an ExecutorService yourself that will run the task in the same thread. For example:

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

And then you could inherit from AbstractExecutorService and use this implementation.

If you're using Guava, another easy one is to use MoreExecutors.newDirectExecutorService() since that does the same thing without you having to create one yourself.

Rasping answered 31/5, 2018 at 18:3 Comment(2)
and would you suggest using this implementation to mock a ScheduledExecutorService as well?Brundage
In my case I explicitly want to test a snippet running in another thread, as that thread won't have the JPA EntityManager of the original thread. Just wanted to mention that this is not a solution for such a case.Endodermis
S
13

Google Guava provides a great class called MoreExecutors which helped me out when testing code that runs in parallel threads via Executor or ExecutorService in JUnit. It lets you create Executor instances that just run everything in the same thread, essentially as a mock of a real Executor. The issue is when things get run in other threads that JUnit isn't aware of, so these Executors from MoreExecutors make everything much easier to test since it's not actually parallel in another thread.

See the MoreExecutors documentation https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/MoreExecutors.html

You can modify your class constructor, or add a new constructor that you only use in tests, which lets you provide your own Executor or ExecutorService. Then pass in the one from MoreExecutors.

So in the test file you'd create the mock executor using MoreExecutors

ExecutorService mockExecutor = MoreExecutors.newDirectExecutorService();

// or if you're using Executor instead of ExecutorService you can do MoreExecutors.newDirectExecutor()

MyService myService = new MyService(mockExecutor);

Then in your class, you only create a real Executor if it wasn't provided in the constructor

public MyService() {}
    ExecutorService threadPool;

    public MyService(ExecutorService threadPool) {
        this.threadPool = threadPool;
    }

    public void someMethodToTest() {
        if (this.threadPool == null) {
            // if you didn't provide the executor via constructor in the unit test, 
            // it will create a real one
            threadPool = Executors.newFixedThreadPool(3);
        }
        threadPool.execute(...etc etc)
        threadPool.shutdown()
    }
}
Sinnard answered 28/12, 2018 at 18:52 Comment(1)
The shutdown call there will effectively cause that method to "block" until the Runnable completes though, you may want to call shutdown in your unit test instead...Nansen
N
4

You could use the Future instance returned by executorService.submit(R).

From documentation:

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#submit(java.lang.Runnable)

Submits a Runnable task for execution and returns a Future representing that task. The Future's get method will return null upon successful completion.

Example:

@Test
void test() throws ExecutionException {
    Future<Boolean> result = Executors.newSingleThreadExecutor().submit(() -> {
        int answer = 43;
        assertEquals(42, answer);
        return true;
    }
    assertTrue(result.get());
}

The inner assertion will throw an exception, which causes result.get() to throw its own exception. Thus the test will fail, and the exception's cause will tell you why ("Expected 42, but was 43 instead").

Novia answered 24/9, 2017 at 19:9 Comment(2)
This is pretty much how I solved it, and is in my opinion the best answer among the ones proposed here.Lourielouse
Returning a Future and calling get on it works, or returning a straight Thread object and calling join on it (if you can get away without using an ExecutorService), or calling shutdown on the ExecutorService (same thing, does a join)Nansen
S
1

A few options:

  • Extract the code out of the executor service and test it 'stand alone' i.e in your example test executeTask1() and executeTask2() on their own or even together but just not by executing them in a separate thread. The fact that you are "passing an executor service object into this constructor class" helps here since you could have

    • A test which mocks the executor service and verifies that you submit the correct runnable to it
    • Test(s) which verify the behaviour of executeTask1() and executeTask2() without running them in a separate thread.
  • Use a CountDownLatch to allow your code-in-executor-service to indicate to the test thread when it is finished. For example:

    // this will be initialised and passed into the task which is run by the ExecutorService 
    // and will be decremented by that task on completion
    private CountDownLatch countdownLatch; 
    
    @Test(timeout = 1000) // just in case the latch is never decremented
    public void aTest() {
        // run your test
    
        // wait for completion
        countdownLatch.await();
    
        // assert 
        // ...
    }
    
  • Accept that you have to wait for the other thread to complete and hide the ugliness of Thread.sleep calls in your test cases by using Awaitility. For example:

    @Test
    public void aTest() {
        // run your test
    
        // wait for completion
        await().atMost(1, SECONDS).until(taskHasCompleted());
    
        // assert 
        // ...
    }
    
    private Callable<Boolean> taskHasCompleted() {
        return new Callable<Boolean>() {
            public Boolean call() throws Exception {
                // return true if your condition has been met
                return ...;
            }
        };
    }
    
Spectrohelioscope answered 24/9, 2017 at 19:12 Comment(2)
But note that my methods, say here executeTask1() and executeTask2(), here are private methods and they don't have any return types. I am not finding this way of testing reliable. Can you suggest why this is reliable way ?Incomparable
I don't think I can offer much help on the specifics of testing executeTask1() and executeTask2() without seeing their implementation. I don't think I can offer any help with this: "I am not finding this way of testing reliable" without seeing what you have tried so far. If you are trying to rework your code to facilitate either option 1 or option 2 from my answer then perhaps a separate question which addresses your specific issues and provides a MCVE might help?Spectrohelioscope
H
1

I agree with the comment of @ejfrancis, in my case what I did, is that since it was a local variable, I would move it to be a member variable and from there, I would simply use reflection in the test (probably reflection is not the best approach to go, but it will be less changes)

class MyClass {
            private final ExecutorService executorService = 
            Executors.newFixedThreadPool(5);;
}

Then from here I would just go in my test after the creation of the class like this:

@BeforeEach
void init(){
   MyClass myClass = new Class();
   ReflectionTestUtils.setField(myClass, "executorService", MoreExecutors.newDirectExecutorService());
}
Hamelin answered 25/11, 2019 at 19:17 Comment(1)
Did you know that ‘I’ is written as a capital letter? Apart from that, good to see ReflectionTestUtils, but inversion of control is still much better.Danilodanio
N
1

Since you control the Executor, you can issue a shutdown on it:

ExecutorService myExecutorService = ...;
mTestObject = new TestObject(myExecutorService);
mTestObject.test();
myExecutorService.shutdown(); // this will wait for submitted jobs to finish, like a join
// now do asserts

Or even if you have a getter for it, possibly same deal.

If you're real desperate you may be able to use a ThreadPoolExecutor instantiation of ExecutorService which has a getActiveCount() method and wait till it drops to zero...

Nansen answered 4/10, 2021 at 17:9 Comment(0)
D
1

Just use executor.awaitTermination(1, SECONDS) as this will block and execute tasks waiting for execution after that you can see result and continue test code like in this one:

@Test
fun testFileJsonStore() {
    val store = CSFileJsonStore(context, "file")
    val property: StoreTypesTestData by store.property("property")
    assertEquals(5, property.int)

    property.string = "new value"
    property.int = 123
    property.jsonObject.lateString = "new value"
    executor.awaitTermination(1, SECONDS)
    assertEquals(
        """{"property":{"key1":"new value","key2":123,"key3":{"lateStringId":"new value"}}}""",
        store.file.readString())

    val store2: CSStore = CSFileJsonStore(context, "file")
    val property2: StoreTypesTestData by store2.property("property")
    assertEquals("new value", property2.string)
    assertEquals(123, property2.int)
    assertEquals("new value", property2.jsonObject.lateString)
}
Dupree answered 27/7, 2022 at 0:28 Comment(0)
D
1

The Spring Framework provides SyncTaskExecutor which implements TaskExecutor hence Executor. It simply executes the Runnable right away

    @Override
    public void execute(Runnable task) {
        Assert.notNull(task, "Runnable must not be null");
        task.run();
    }

In order to make all Spring @Services make use of this Executor you can provide a @TestConfiguration class somewhere in the src/test/java folder.

@TestConfiguration
public class TestConfig {
  
  @Bean
  public Executor syncTaskExecutor() {
    return new SyncTaskExecutor();
  }
}

@TestConfiguration is very useful for overriding some @Beans only for the scope of test execution. According to its documentation:

@Configuration that can be used to define additional beans or customizations for a test. Unlike regular @Configuration classes the use of @TestConfiguration does not prevent auto-detection of @SpringBootConfiguration.

After that the test class should be annotated with @ContextConfiguration(classes = TestConfig.class)

Desmarais answered 22/3, 2023 at 10:0 Comment(0)
N
0

If you control what abd is and can make it a mock, you could set a timeout that it be called eventually: Mockito: wait for an invocation that matches arguments

Nansen answered 4/10, 2021 at 17:23 Comment(0)
B
0

If you are using Kotlin with mockk, mocked ScheduledExecutorService is the way to go.

class MyExecutorClass() {
    fun executeSomething() {
        Executors.newSingleThreadScheduledExecutor().schedule( {
                // do something here
            }, 3, TimeUnit.SECONDS)
    }
}
       
@Test
fun myExecutorTest() {
    // setup
    val capturingSlotRunnable: CapturingSlot<Runnable> = slot()
    mockkStatic(Executors::class)
    val mockExecutor: ScheduledExecutorService = mockk(relaxed = true)
    every { Executors.newSingleThreadScheduledExecutor() } returns mockExecutor
    every { mockExecutor.schedule(capture(capturingSlotRunnable), any(), any()) } returns null
    
    // execute
    val myExecutor = MyExecutorClass()
    myExecutor.executeSomething()
    val runnable = capturingSlotRunnable.captured
    runnable.run()

    // verify
    // TODO: verify the executable block did whatever it was supposed to
}
Bacchius answered 29/9, 2022 at 21:25 Comment(0)
C
0
@Mock
ExecutorService executorService;


@Test
public void testExecutorService() throws InterruptedException, ExecutionException {
    // Set the Mock ExecuotService to your testing class or service
    ReflectionTestUtils.setField(service, "executorService", executorService);
    
    // Mock data 
    SomeEntity entity = buildSomeEntity();
    
    doAnswer(invocation -> {
        // This is to mock your actual task execution
        when(respository.save(any(Entity.class))).thenReturn(entity);
        
        Runnable runnable = invocation.getArgument(0);
        runnable.run();
        return null;
    }).when(executorService).submit(any(Runnable.class));

    service.createEntity(Params....);
Colvert answered 15/3 at 21:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.