Как изменить статическое приватное поле во время тестов? - PullRequest
0 голосов
/ 09 декабря 2018

Мой проект использует JUnit, Mockito, PowerMockito для создания модульного теста.Код, приведенный ниже:

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 голосов
/ 13 декабря 2018

Как указано в комментариях, «реальным» решением было бы изменить дизайн так, чтобы его было проще тестировать.Но, учитывая ваши ограничения, вот альтернативная идея, что может тоже может работать.

Видите ли, вы заполняете свой список, используя

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

Итак, теоретическиВы можете использовать PowerMock (ito), чтобы использовать Arrays.asList() в качестве точки, где вы берете на себя контроль.Другими словами: вы можете asList() вернуть любой список, который вы хотите использовать для тестирования!

Конечно, лучшим подходом может быть замена статического списка внутри чего-то, что может быть внедрено, например

private final ServicesProvider serviceProvider; 

, где у вас есть отдельный класс, который дает вам такой список.Вы можете протестировать этот класс самостоятельно, а затем использовать обычный Mockito, чтобы подключить к вашему тестируемому коду провизируемый поставщик услуг.

0 голосов
/ 15 декабря 2018

Я обнаружил, что решение использует аннотацию @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));
    }
}
0 голосов
/ 10 декабря 2018

Итак, вы знаете, как издеваться над ServiceExecutor.execute, но не хотите имитировать.Вы хотите выполнить его в тестах, но без запуска всех методов service.execute () в тесте.Это не тест для FirstController, а тест для ServiceExecutor.Таким образом, вы можете свести свой вопрос к этому.

Вы можете использовать Reflection, чтобы изменить значение закрытых статических полей 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();
            }
        }
    }
}
...