вызов статического метода в закрытом статическом финальном поле класса - PullRequest
0 голосов
/ 30 июня 2018

У меня есть базовый Widget класс, который использует служебный класс Utils

public class Widget {

    private static final String BUTTON_KEY = Utils.getMessage("btn-key");

    public boolean comp() {
        String specialKey = Utils.getMessage("special-key");
        return specialKey.equals(BUTTON_KEY);
    }

}

class Utils {

    public static String getMessage(String key) {
        return key + " : message";
    }

}

Я хочу создать тест для метода comp.

Мой тест выглядит так

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public class WidgetTest {

    private Widget widget;

    @Before
    public void setUp() {
        mockStatic(Utils.class);
        widget = new Widget();
    }

    @Test
    public void testComp() {
        expect(Utils.getMessage("btn-key")).andReturn("btn-key : message");
        expect(Utils.getMessage("special-key")).andReturn("special-key : message");
        replayAll();
        assertFalse(widget.comp());
        verifyAll();
    }

}

Тест не пройден с

java.lang.IllegalStateException: missing behavior definition for the preceding method call:
Utils.getMessage("btn-key")
Usage is: expect(a.foo()).andXXX()

Если я удаляю вызов метода в поле константы (и, следовательно, удаляю ожидание для него), тест завершается успешно.

В чем проблема?

1 Ответ

0 голосов
/ 01 июля 2018

Общий комментарий

Пожалуйста, прочитайте этот философский комментарий перед остальной частью ответа.

Я часто сталкиваюсь с плохо проверяемым кодом. У нас есть Spring, у нас есть Dependency Injection, почему мы все еще чувствуем необходимость использовать статические методы и служебные классы?

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

Тот факт, что вам нужно высмеивать ваш метод, означает, что метод на самом деле не статический . Ну, может быть Math.sqrt() или Assert.assertEquals() являются хорошими примерами действительно статических методов.

С другой стороны, некоторые методы, которые на первый взгляд могут показаться «статичными», предают вас, как только вы начинаете думать о тестировании, например LocalDate.now(). В ваших тестах вам нужно иметь статическую текущую дату, а не метод:)

Здесь много хороших идей: https://softwareengineering.stackexchange.com/q/148049/105827


Ваша проблема

Проблема в том, что статический инициализатор класса Widget называется до того, как вы начнете определять ожидания.

Вы можете увидеть это, если добавите этот код где-нибудь в класс Widget:

static {
    System.out.println("Widget.static");
}

и этот код к началу WidgetTest.testComp() метода:

public void testComp() throws Exception {
    System.out.println("test method");
    ... // the rest of the method
}

Когда вы запускаете тест, вывод выглядит так:

Widget.static
test method

Это означает, что BUTTON_KEY = Utils.getMessage("btn-key") выполняется до expect(Utils.getMessage("btn-key")).andReturn("btn-key : message"); и что PowerMock справедливо жалуется на отсутствующее определение поведения.


Возможное быстрое решение

Если вы хотите сохранить свою статическую логику, есть быстрый обходной путь. Не запускайте BUTTON_KEY в блоке статического инициализатора, но лениво.

Мне это не очень нравится, я все же предпочитаю полностью избавляться от статических вызовов.

Я оставил в коде тест println() с, чтобы вы могли видеть порядок вызовов.

public class Widget {
    private static String BUTTON_KEY;

    static {
        System.out.println("Widget.static");
    }

    public boolean comp() {
        String specialKey = Utils.getMessage("special-key");
        return specialKey.equals(getButtonKey());
    }

    private static String getButtonKey() {
        synchronized (Widget.class) {
            if (BUTTON_KEY == null) {
                System.out.println("Widget is calling Utils.getMessage(`btn-key`)");
                BUTTON_KEY = Utils.getMessage("btn-key");
            }
        }
        return BUTTON_KEY;
    }
}

class Utils {
    public static String getMessage(String key) {
        return key + " : message";
    }
}

Тогда порядок звонков таков:

Widget.static
test method
Widget is calling Utils.getMessage(`btn-key`)
...