簡體   English   中英

測試期間如何修改靜態私有字段?

[英]How to modify a static private field during tests?

我的項目使用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()方法。

調用execute()方法時,我嘗試模擬ServiceExecutordoNothing() 但是我對ServiceExecutorprivate static final List<Service> services有問題。 它總是為每個服務構造新的實例。 每個服務創建新實例的時間更長,如果我模擬每個Service ,我不知道它們以后將擁有多少Service

您是否有想法在不運行ServiceExecutor任何方法的情況下在FirstController驗證ServiceExecutor.execute()

因此,您知道如何模擬ServiceExecutor.execute,但是您不想模擬它。 您想在測試中執行它,但不運行測試中的所有service.execute()方法。 那不是對FirstController的測試,而是對ServiceExecutor的測試。 因此,您可以將問題簡化為該問題。

您可以按如下所述使用反射在測試中更改私有靜態字段ServiceExecutor.services的值: 使用Java反射更改私有靜態最終字段。

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

如評論中所述,“真正的”解決方案是將設計更改為更易於測試的設計。 但考慮到你的限制,這里也許是一個可以工作,另外一個設想,太。

您看到的是,您正在使用

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

因此,從理論上講,您可以使用PowerMock(ito)來使用Arrays.asList()作為接管控制的點。 換句話說:您可以讓asList()返回要用於測試的任何List!

當然,更好的方法可能是將靜態列表替換為可以注入的內容,例如

private final ServicesProvider serviceProvider; 

在這里,您有一個獨特的類,可以為您提供此類列表。 您可以單獨測試該類,然后使用普通的Mockito將模擬的服務提供程序添加到要測試的代碼中。

我發現該解決方案使用@SuppressStaticInitializationFor批注。

使用此注釋可以抑制一個或多個類的靜態初始化器(構造函數)。

之所以需要注釋,是因為我們需要在加載時知道是否應跳過此類的靜態構造函數執行。 不幸的是,我們無法將類作為值參數傳遞給批注(並因此獲得類型安全的值),因為這樣,在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));
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM