NullPointerException при попытке вызвать метод на ложном Eclipse Paho MqttClient - PullRequest
0 голосов
/ 22 мая 2018

У меня есть следующий, сильно упрощенный класс:

public class MyClass
{
    private MqttClient field;

    public void test() throws MqttException
    {
        field = new MqttClient(null, null);
        field.connect();
    }
}

Я использую Eclipse Paho's MqttClient класс.

Я хочу проверить метод test с помощью UnitTest.Чтобы иметь возможность управлять экземпляром MqttClient, я хочу смоделировать объект.Мой тестовый класс выглядит следующим образом:

public class TestMyClass
{
    // Make sure we are running with the PowerMockAgent initialized
    static
    {
        PowerMockAgent.initializeIfNeeded();
    }

    // Make sure we are using PowerMock
    @Rule
    public PowerMockRule    rule    = new PowerMockRule();

    // Mocked MqttClient
    @Mock
    MqttClient              field;

    // MyClass-Object injected with mocks.
    @InjectMocks
    MyClass                 myClass = new MyClass();

    @PrepareForTest(MyClass.class)
    @Test
    public void test() throws Exception
    {
        // Initialize our mocks from annotations
        MockitoAnnotations.initMocks(this);

        // Catch the "new MqttClient(null, null);"
        PowerMockito.whenNew(MqttClient.class).withAnyArguments().thenReturn(field);


        myClass.test();

    }
}

Когда я выполняю свой тест, у меня возникает следующая ошибка:

java.lang.NullPointerException
    at test.java.test.MyClass.test(MyClass.java:13)
    at test.java.test.TestMyClass.test(TestMyClass.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.powermock.modules.junit4.rule.PowerMockStatement.evaluate(PowerMockRule.java:73)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

Я могу вызвать любой метод на field, этовсегда нулевая ссылка.Моя проблема в том, что я понятия не имею, почему это так.

Я изменил тип field на несколько других классов, включая java.awt.point и SerializationInstantiatorHelper (чтобы проверить, связано ли это с MqttClient, являющимся внешней зависимостью), а также с несколькими классами в моем проекте, вызвав соответствующий метод для этих объектов.Во всех этих случаях проблема не существовала, и тест прошел без проблем.Метод на объекте mock вызывается и ничего не делает - так, как я хочу.

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

Я, честно говоря, полностью потерян в этот момент.Что делает класс MqttClient особенным?Почему я не могу издеваться над другими классами?Что я пропустил?

// EDIT

Я сузил проблему, создав локальную версию MqttClient, скопировав исходный код.Честно говоря, сейчас я еще больше запутался.Класс имеет три конструктора со следующими сигнатурами:

1.)

public MqttClient(  String serverURI,
                    String clientId) throws MqttException

2.)

public MqttClient(  String serverURI,
                    String clientId,
                    MqttClientPersistence persistence) throws MqttException

3.)

public MqttClient(  String serverURI,
                    String clientId,
                    MqttClientPersistence persistence,
                    ScheduledExecutorService executorService) throws MqttException

Если удалить один из этих трех конструкторов, все работает. Любая комбинация класса с двумя из этих трех конструкторов работает нормально. Как только все три конструктора существуют, появляется упомянутое сообщение об ошибке.Это приводит меня к предположению, что это может быть связано с PowerMockito, не найдя правильный конструктор?Если так, как этого можно избежать?

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Я думаю, что вы столкнулись с той же проблемой, что и описанная в этом выпуске

Насмешка, когда использование NewAnyArguments не соответствует пустым аргументам # 891

Проблемаэто если объявлено несколько конструкторов:

Соответствия первого конструктора сгенерированы правильно (используя форму или (isA (Class), isNull ())).Но остальные конструкторы не являются (они имеют только какой-либо (класс), который заставляет их не совпадать со значениями NULL).

Поэтому, пока проблема не будет решена, вы можете использовать обходной путь:

MqttClient field = mock(MqttClient.class);

вместо @Mock MqttClient field;

0 голосов
/ 28 августа 2018

Испытанная цель, похоже, настроена неправильно, поэтому при выполнении теста она ведет себя не так, как ожидалось.

Иногда проще придерживаться простых правил (KISS).

@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class TestMyClass {

    @Test
    public void test() throws Exception {
        //Arrange

        // mock MqttClient
        MqttClient field = mock(MqttClient.class);
        // setup the "new MqttClient(null, null);"
        PowerMockito.whenNew(MqttClient.class).withArguments(isNull(), isNull()).thenReturn(field);

        MyClass subject = new MyClass();

        //Act
        subject.test();

        //Assert
        PowerMockito.verifyNew(MqttClient.class).withArguments(isNull(), isNull());
        verify(field).connect();
    }
}

Используя .withArguments с соответствующими аргументами или сопоставителями, это помогает сузить, какой именно конструктор нужно смоделировать для теста, избегая любых конфликтов, когда существует несколько конструкторов.

На основе упрощенного класса вВ тесте, показанном в примере, не было введено никаких зависимостей, так как оно инициализировало зависимость локально (т. е. тесная связь), поэтому на самом деле не было необходимости вставлять макеты в тестируемый класс.

Кроме того, PrepareForTest обычно выполняется на тестовом классе, а не на методе тестирования в этом случае, как согласно документации

Все виды использования требуют @RunWith(PowerMockRunner.class) и @PrepareForTest с комментариями на уровне класса .

мина

...