Search code examples
javaspringmockitohazelcastexecutor

mockito test invocation into abstract executor


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?


Solution

  • 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);
    }