0

My project uses JUnit, Mockito, PowerMockito to create a unit test. The code as below:

public class FirstController {
    public void doSomething() {
        ServiceExecutor.execute();
    }
}

public class ServiceExecutor {

    private static final List<Service> services = Arrays.asList(
        new Service1(),
        new Service2(),
        ...
    );

    public static void execute() {
        for (Service s : services) {
            s.execute();
        }
    }   
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({ServiceExecutor.class})
public class FirstControllerTest {

        @Before
        public void prepareForTest() {
            PowerMockito.mockStatic(ServiceExecutor.class);
            PowerMockito.doNothing().when(ServiceExecutor.class)
        }

        @Test
        public void doSomethingTest() {
            FirstController firstController = new FirstController();
            firstController.doSomething();
            PowerMockito.verifyStatic(ServiceExecutor.class, Mockito.times(1));
        }   
    }

The full source code of this issue: https://github.com/gpcodervn/Java-Tutorial/tree/master/UnitTest

I want to verify the ServiceExecutor.execute() method which was run.

I tried to mock ServiceExecutor and doNothing() when the execute() method is called. But I have a problem with the private static final List<Service> services in the ServiceExecutor. It always constructs the new instance for each Service. Each service is longer to create the new instance and I don't know how many Service they will have later if I mock each Service.

Do you have any idea to verify ServiceExecutor.execute() in FirstController without run any method in ServiceExecutor?

Giang Phan
  • 464
  • 5
  • 14
  • 1
    If you're creating an instance of `ServiceExecutor` why do need the `execute` method to be `static` in the first place? – QBrute Dec 09 '18 at 16:19
  • Thanks, @QBrute. This is my mistake. I've already updated the code. – Giang Phan Dec 09 '18 at 16:35
  • 2
    Honestly, refactor to avoid global state before considering testing. (Alternatively, don't use global state too start with.) – Tom Hawtin - tackline Dec 09 '18 at 16:36
  • There is no way. Because this service is created by other projects. In my controller, I want to test this method, I must mock this Service. – Giang Phan Dec 09 '18 at 16:39
  • add the test code you've written so far. Did the related questions listed not help? – tkruse Dec 10 '18 at 04:41
  • Thanks, @tkruse. I've already added the unit test code. – Giang Phan Dec 10 '18 at 07:24
  • Possible duplicate of [How do I mock a static method that returns void with PowerMock?](https://stackoverflow.com/questions/9585323/how-do-i-mock-a-static-method-that-returns-void-with-powermock) – tkruse Dec 10 '18 at 08:50

3 Answers3

0

So you know how to mock ServiceExecutor.execute, but you do not want to mock it. You want to execute it in the tests, but without running all the service.execute() methods in the test. That is not a test for FirstController, but a test for ServiceExecutor. So you can reduce your question to that.

You can use Reflection to change the value of the private static fields ServiceExecutor.services in the tests as described here: Change private static final field using Java reflection

public class ServiceExecutorTest {

    @Test
    public void doSomethingTest() throws NoSuchFieldException, IllegalAccessException {
        Field field = null;
        List<Service> oldList = null;
        try {
            field = ServiceExecutor.class.getDeclaredField("services");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

            final Service serviceMock1 = mock(Service.class);
            final Service serviceMock2 = mock(Service.class);
            final List<Service> serviceMockList = Arrays.asList(serviceMock1, serviceMock2);
            oldList = (List<Service>) field.get(null);
            field.set(null, serviceMockList);
            ServiceExecutor.execute();
            // or testing the controller
            // FirstController firstController = new FirstController();
            // firstController.doSomething();
            verify(serviceMock1, times(1)).execute();
            verify(serviceMock2, times(1)).execute();
        } finally {
            // restore original value
            if (field != null && oldList != null) {
                field.set(null, oldList);
            }
        }
    }

    static class Service {
        void execute() {
            throw new RuntimeException("Should not execute");
        }
    }

    static class ServiceExecutor {

        private static final List<Service> services = Arrays.asList(
                new Service());

        public static void execute() {
            for (Service s : services) {
                s.execute();
            }
        }
    }
}
tkruse
  • 8,363
  • 5
  • 43
  • 70
  • Thank you for your support. I really want to verify the ServiceExecutor.execute() method which was run, my code sample above is a mistake. I've already run your code but it still has the same problem with my question. The instance of each service are also invoked because of the private static final field. – Giang Phan Dec 10 '18 at 14:09
  • Thank you for your time @tkruse. However, it's not as I expected. Your code is written for ServiceExecutor class, I want to test the FirstController class. Besides, you can add the output log on the constructor of Service class, you will see it also call the real constructor. Even if you can mock this Service it's also not meet with my question "Each service is longer to create the new instance and I don't know how many Service they will have later if I mock each Service." – Giang Phan Dec 13 '18 at 14:26
  • to easier for everyone to understand correctly my code. I've already updated the source on GitHub. Please take a look at the question. Here is your code: https://github.com/gpcodervn/Java-Tutorial/blob/master/UnitTest/src/test/java/com/gpcoder/ServiceExecutorTest.java. – Giang Phan Dec 13 '18 at 14:47
  • IN yourfirst comment you said: " I really want to verify the ServiceExecutor.execute() method which was run, my code sample above is a mistake." Now you say " I want to test the FirstController class". Make up your mind. I think it is impossible to load the ServiceExecutorClass without executing the initializer for static variables, so the constructors will be called in any case when you import the ServiceExecutor class anywhere. – tkruse Dec 14 '18 at 00:32
  • Maybe we misunderstand, as the question section, I already created a test class for FirstController, not ServiceExecutor. Thank you for your time. – Giang Phan Dec 14 '18 at 02:19
0

As expressed in comments, the "real" solution would be to change the design to maker it easier to test. But given your constraints, here is an alternative idea that might could work, too.

You see, you are filling your list using

private static final List<Service> services = Arrays.asList(...)

So, theoretically, you could use PowerMock(ito) to use Arrays.asList() as the point where you take over control. In other words: you could have asList() return whatever List you want to be used for testing!

Of course, the better approach could be to replace that static list within something that could be injected, like

private final ServicesProvider serviceProvider; 

where you have a distinct class that gives you such a list. You can test that class on its own, and then use ordinary Mockito to get a mocked service provider into your code under test.

GhostCat
  • 127,190
  • 21
  • 146
  • 218
  • Thanks, @GhostCat. Your idea about using the PowerMockito to mock Arrays.asList(), I've already tried it before. But it's also not working, please take a look: https://github.com/gpcodervn/Java-Tutorial/blob/master/UnitTest/src/test/java/com/gpcoder/FirstControllerTest_MockArrays.java – Giang Phan Dec 13 '18 at 14:39
  • About replace static list within something that could be injected, I can't modify any code because it depend on other projects and other team. So, I want to find the way to mock the static final field. – Giang Phan Dec 13 '18 at 14:41
  • I understood that. But answers should also be written for future readers. Not everybody is under the same constraints! For your case, as said, you could try to control Arrays.toList(). That would be much easier compared to interfering multiple calls to `new ServiceX()`! Of course, that approach ties you to Arrays.asList() being used. – GhostCat Dec 13 '18 at 14:46
  • Please take a look at the link on the comment above. It's not working, are there any mistake of my code? – Giang Phan Dec 13 '18 at 14:51
  • Update: please understand that is asking a bit much. Linking to some external site, and giving "not working" as problem description is really not a good starting point here. – GhostCat Dec 14 '18 at 12:58
  • "Not working" it's mean in the context we are talking about mock an Arrays.asList() method. It still not resolves my issue as I asked on the question section "It always constructs the new instance for each Service.". – Giang Phan Dec 15 '18 at 04:53
0

I've found the solution uses the @SuppressStaticInitializationFor annotation.

Use this annotation to suppress static initializers (constructors) for one or more classes.

The reason why an annotation is needed for this is because we need to know at load-time if the static constructor execution for this class should be skipped or not. Unfortunately, we cannot pass the class as the value parameter to the annotation (and thus get type-safe values) because then the class would be loaded before PowerMock could have suppressed its constructor.

https://github.com/powermock/powermock/wiki/Suppress-Unwanted-Behavior

The final code:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ ServiceExecutor.class })
@SuppressStaticInitializationFor("com.gpcoder.staticblock.ServiceExecutor")
public class FirstControllerTest {

    @Before
    public void prepareForTest() throws Exception {
        PowerMockito.mockStatic(ServiceExecutor.class);
        PowerMockito.doNothing().when(ServiceExecutor.class);
    }

    @Test
    public void doSomethingTest() {
        FirstController firstController = new FirstController();
        firstController.doSomething();
        PowerMockito.verifyStatic(ServiceExecutor.class, Mockito.times(1));
    }
}
Giang Phan
  • 464
  • 5
  • 14