测试期间如何修改静态私有字段?

时间:2018-12-09 15:59:33

标签: java junit mockito powermock

我的项目使用JUnitMockitoPowerMockito创建单元测试。代码如下:

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

此问题的完整源代码:https://github.com/gpcodervn/Java-Tutorial/tree/master/UnitTest

我想验证运行的ServiceExecutor.execute()方法。

在调用ServiceExecutor方法时,我尝试模拟doNothing()execute()。但是我对private static final List<Service> services中的ServiceExecutor有疑问。它总是为每个服务构造新的实例。每个服务创建新实例的时间更长,如果我模拟每个Service,我以后将不知道它们将提供多少服务。

您有没有在ServiceExecutor.execute()中运行任何方法的情况下验证FirstController中的ServiceExecutor的想法?

3 个答案:

答案 0 :(得分:0)

因此,您知道如何模拟ServiceExecutor.execute,但您不想模拟它。您想在测试中执行它,但不运行测试中的所有service.execute()方法。那不是对FirstController的测试,而是对ServiceExecutor的测试。因此,您可以将问题简化为该问题。

您可以使用反射来更改测试中专用静态字段ServiceExecutor.services的值,如下所述: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();
            }
        }
    }
}

答案 1 :(得分:0)

如评论中所述,“真正的”解决方案是将设计更改为易于测试的制造商。但是鉴于您的限制,这是可能也可以起作用的另一种想法。

您看到的,您正在使用来填充列表

lat

因此,从理论上讲,您可以使用PowerMock(ito)来使用private static final List<Service> services = Arrays.asList(...) 作为控制点。换句话说:您可以让Arrays.asList()返回想要用于测试的任何列表!

当然,更好的方法可能是将静态列表替换为可以注入的内容,例如

asList()

,其中有一个独特的类为您提供了这样的列表。您可以单独测试该类,然后使用普通的Mockito将模拟的服务提供程序添加到要测试的代码中。

答案 2 :(得分:0)

我发现解决方案使用了@SuppressStaticInitializationFor注释。

  

使用此注释可以抑制静态初始化器(构造函数)   一门或多门课。

     

之所以需要注释,是因为我们需要   在 load-time 知道此类的静态构造函数执行   是否应该跳过。不幸的是,我们不能通过   注释的value参数(并因此获得类型安全的值)   因为这样一来,该类将在PowerMock可以加载之前加载   取消了其构造函数。

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

最终代码:

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