Mocking not working with ExecutorService.submit()

199 views Asked by At

While working with Executor Service mocking is not working. Following is the code.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DemoClass {
    public void demoMethod() {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        List<MyUser> myUserList = new ArrayList();
        myUserList.add(new MyUser("ABC"));
        myUserList.add(new MyUser("XYZ"));
        myUserList.forEach(transaction -> executor.submit(() -> processList(transaction)));

        // this works well and print "mockedName"
        // processList(new MyUser("withoutExecutor"));
        executor.shutdown();
    }

    public void processList(MyUser user){
        String name = MyEmployee.getEmployeeName();
        System.out.println(name);
    }
}
***********************************************
public class MyEmployee {
    public static String getEmployeeName() {
        return "testEmpName";
    }
}
***********************************************
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import static org.mockito.Mockito.mockStatic;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class DemoClassTest {
    private DemoClass demoClass;
    private MockedStatic<MyEmployee> myEmployeeMockedStatic;
    @Before
    public void before() throws Exception {
        demoClass = new DemoClass();
        myEmployeeMockedStatic = mockStatic(MyEmployee.class);
    }

    @Test
    public void myTest() {
        // given
        myEmployeeMockedStatic.when(MyEmployee::getEmployeeName).thenReturn("mockedName");
        // when
        demoClass.demoMethod();
    }
    @After
    public void after() {
        myEmployeeMockedStatic.close();
    }
}

It is printing "testEmpName", desired result is "mockedName" Any static method called inside the calling of submit methods is not getting mocked. It could be because of threading in a pool, but how we can mock the static methods in this case?

Findings:

  1. No any mock will work inside the executor.submit()

  2. Reason of this is - mocking is not working on the different thread.

  3. While we do executor.submit() - it starts the process on the different thread- that is the reason mocking is not working

  4. I can see few posts that indicates that is not supported by the mocking till now. https://github.com/mockito/mockito/issues/2142

  5. However I understand we can find some workaround - like we can mock executor and pass our required mocking in that mock. Still not sure on how to do.

  6. Even if someone decodes the following for the above code. Referece: https://github.com/mockito/mockito/issues/2142

Executor executor = you executor....;
doAnswer(new Answer<Object>() {
    public Object answer(InvocationOnMock invocation)
        throws Exception {
        Object[] args = invocation.getArguments();
        Runnable runnable = (Runnable)args[0];
        try (MockedConstruction<SomeClass> mockedConstructor = Mockito.mockConstruction(SomeClass.class);
              MockedStatic<SomeStaticUtil> mockedUtil = mockStatic(SomeStaticUtil.class))
        {
            ....do mocks things
            runnable.run();
        }
        return null;
    }
}).when(executor).execute(any());
1

There are 1 answers

0
Lesiak On

One workaround is to pass in a different ExecutorService or Executor, which executes the tasks on the same thread.

Note that your code needs to be refactored a bit - you need to pass in Executor to your DemoClass, which will change it's runtime behaviour.

In your case, Executor is enough, so you can use simple implementation:

Executor executor = Runnable::run;

For more options, see Is there an ExecutorService that uses the current thread?

Entire implementation:

DemoClass

public class DemoClass {

    private final Executor executor;

    public DemoClass() {
        this(Executors.newFixedThreadPool(2));
    }

    public DemoClass(Executor executor) {
        this.executor = executor;
    }

    public void demoMethod() {
        List<MyUser> myUserList = new ArrayList<MyUser>();
        myUserList.add(new MyUser("ABC"));
        myUserList.add(new MyUser("XYZ"));
        myUserList.forEach(user -> executor.execute(() -> processList(user)));
    }

    public void processList(MyUser user){
        String name = MyUser.getEmployeeName();
        System.out.println(name);
    }
}

DemoClassTest

public class DemoClassTest {
    private final Executor executor = Runnable::run;
    private final DemoClass demoClass = new DemoClass(executor);

    @Test
    public void myTest() {
        try(MockedStatic<MyUser> mUserMockedStatic = mockStatic(MyUser.class)) {
            // given
            mUserMockedStatic.when(MyUser::getEmployeeName).thenReturn("mockedName");
            // when
            demoClass.demoMethod();
        }
    }
}

Notes:

  • Creating ExecutorService in method using it and immediately shutting it down is less common that having one passed in - at least in the code I've seen so far, but it really depends on your use case.
  • static getEmployeeName makes little sense - but I assume this is only an example
  • Mocking ExecutorService to work in the same thread, as proposed in your question, makes little sense - same thread Executors work just as fine.
  • If launching myltiple threads is essential, you can try creating an Executor which wraps the passed-in Runnable with additional logic - adding and closing mocks. Thus Mockito logic will run on each thread.