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?
-
Possible cross-site duplicate: JUnit testing an asynchronous method with MockitoPhilip Kendall– Philip Kendall2022年01月05日 16:45:16 +00:00Commented 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– Doc Brown2022年01月05日 17:39:24 +00:00Commented 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.Kilian Foth– Kilian Foth2022年01月05日 17:50:17 +00:00Commented Jan 5, 2022 at 17:50
1 Answer 1
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
DirectExecutorService
that directly and synchronously runs the argument passed to theexecute
method.DelayedExecutorService
that stores the argument to theexecute
method and offers an extra method to actually run thatRunnable
.
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!).
-
I like that idea with the
DirectExecutorService
currently I kind of do the same thing, but in test code polluting way.Herr Derb– Herr Derb2022年01月06日 12:13:53 +00:00Commented Jan 6, 2022 at 12:13