I am writing test on a legacy code, before refactoring it, this means that I should not change that code... I am using Spring and Hazelcast, and my class is an Hazelcast Listener (if you don't know Hazelcast it does not matter, just be aware that I cannot change the firm of the method).
@Component
public class MyClass implements EntryAddedListener<String, MyEntry> {
@Autowired
@Qualifier( "asyncExecutor" )
private Executor executor;
@Autowired
private ClassToBeCalled classToBeCalled;
@Override
public void entryAdded( final EntryEvent<String, MyEntry> event ) {
executor.execute( () -> {
...
...
classToBeCalled.methodToBeCalled( event.getValue() );
}
} );
}
}
I want to test that when entryAdded
is called, then execute
is invoked, and especially methodToBeCalled
is invoked as well.
I am trying different approaches, but all of them are bumping into some mockito error. This is the last one:
@RunWith(MockitoJUnitRunner.class)
public class MyClass {
@Mock
private Executor asyncExecutor;
@Mock
private ClassToBeCalled classToBeCalled;
@InjectMocks
private MyClass myClass;
@Test
public void entryListenerShouldInvokeTheClassToBeCalled(){
// given
EntryEvent entryEvent = mock(EntryEvent.class);
MyEntry value = mock(MyEntry.class);
when(entryEvent.getValue()).thenReturn(value);
// here some of my tries, all commented because they don't work
// doCallRealMethod().when(asyncExecutor).execute(any(Runnable.class));
// when(executor.execute(any(Runnable.class))).thenCallRealMethod();
// when
myClass.entryAdded(entryEvent);
// then
verify(asyncExecutor, times(1)).execute(any(Runnable.class));
verify(classToBeCalled, times(1)).methodToBeCalled(value);
}
}
basically I cannot verify that the methodToBeCalled
is called, because Executor is an abstract class. I cannot Spy it, I cannot call the real method. Also, the @Qualifier refers to an implementation in a library, which more or less is:
@Bean(name = {"AsyncExecutor"})
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
...
...
executor.initialize();
return executor;
}
Any idea?
You are trying to use too many mocks. You are mocking the Executor
but still expect that to behave as a regular Executor
that obviously isn't going to work.
Instead use the SyncTaskExecutor
which basically makes the call to executor.execute
a synchronous call for your test and only mock the ClassToBeCalled
.
Something like this, using the ReflectionTestUtils
should do the trick.
public class MyClass {
private Executor executor = new SyncTaskExector();
private ClassToBeCalled classToBeCalled;
private MyClass myClass;
@Before
public setup() {
myClass = new MyClass();
classToBeCalled = mock(ClassToBeCalled.class);
RelfectionTestUtils.setField(myClass, "executor", executor);
RelfectionTestUtils.setField(myClass, "classToBeCalled", classToBeCalled);
}
@Test
public void entryListenerShouldInvokeTheClassToBeCalled(){
// given
EntryEvent entryEvent = mock(EntryEvent.class);
MyEntry value = mock(MyEntry.class);
when(entryEvent.getValue()).thenReturn(value);
// when
myClass.entryAdded(entryEvent);
// then
verify(classToBeCalled, times(1)).methodToBeCalled(value);
}
}
The fact that the methodToBeCalled
is being called is also the proof that the execute
method got executed.
HINT: I would suggest changing it to use constructor based injection instead of field injection as that makes testing a lot easier.
@Component
public class MyClass implements EntryAddedListener<String, MyEntry> {
private final Executor executor;
private final ClassToBeCalled classToBeCalled;
@Autowired
public MyClass(@Qualifier("asyncExecutor") Executor executor, ClassToBeCalled classToBeCalled) {
this.executor=executor;
this.classToBeCalled=classToBeCalled;
}
}
Now you can remove the ReflectionTestUtils
and simply construct your object.
@Before
public void setup() {
classToBeCalled = mock(ClassToBeCalled.class);
myClass = new MyClass(executor, classToBeCalled);
}