2

I have a bean which executes a method doStuff asynchronous, which of I am uncertain what is the best way to test the logic of that method doStuff.

public class MyBean {

    private final ExecutorService executorService;
    
    @Inject
    public MyBean(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public void work() {
        doThings();
        executorService.execute(() -> doStuff());
    }
    
    private void doThings(){
        // ....
    }

    private void doStuff(){
        // my code which needs to be run asynchronous
    }
}

I could "open" the method doStuff() to package protected, to be able to make a direct call in my Unit-Test. But changing productive code for testability is a strong bad smell. In my unit test, I currently catch the argument which is given to the executorService mock, execute it and then do my asserts like a regular unit test. No change to the productive code is needed. But the tests seem much harder to read/understand:

@Mock
private ExecutorService executorService;
@InjectMocks
private MyBean myBean;

@Captor
ArgumentCaptor<Runnable> runnableCaptor;

@Test
void MyTest() {
    myBean.work();
    verify(executorService).execute(runnableCaptor.capture());

    runnableCaptor.getValue().run();

    // actually assert outcomings of running doStuff() Method
}

Is this acceptable? It doesn't seem very nice to me. How do I test this code as easy as possible? Are there maybe changes to be made to the productive code?

3
  • Possible cross-site duplicate: JUnit testing an asynchronous method with Mockito Jan 5, 2022 at 16:45
  • "changing productive code for testability is a strong bad smell" - I disagree. When code is written in a hard-to-test manner, and one wants to write tests for it, it should be changed. Maybe one needs to bring some higher level tests first into the system before changing the code, in case you think the risk is too high the change will break anything.
    – Doc Brown
    Jan 5, 2022 at 17:39
  • Performing Work asynchrously rather than directly is a major complication, and the test reflects that. You could make it simpler by inventing wrappers for the "turn asynchronous into synchronous for testing purposes", but then you'd have to understand that wrapping! I actually wouldn't change a thing about this test. Jan 5, 2022 at 17:50

1 Answer 1

4

Unless the aim of your test is to verify how MyBean interacts with the ExecutorService dependency, I would not use a mock for the ExecutorService.

Instead, I would create 2 implementations of the ExecutorService interface

  1. DirectExecutorService that directly and synchronously runs the argument passed to the execute method.
  2. DelayedExecutorService that stores the argument to the execute method and offers an extra method to actually run that Runnable.

In the majority of test cases, where you don't care about execution order or you want to verify that the bean can handle being interrupted by the ExecutorService, inject the DirectExecutorService and write your test based on the knowledge that the asynchronous code will have completed by the time the work method returns.

Then, you should also have some test cases to verify that the bean also works correctly if the asynchronous part is executed with a delay. That is where the DelayedExecutorService comes in and the test code can control when the asynchronous part gets executed (if at all!).

1
  • I like that idea with the DirectExecutorService currently I kind of do the same thing, but in test code polluting way.
    – Herr Derb
    Jan 6, 2022 at 12:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.