【问题标题】:Mockito - NullpointerException when using doAnswer().when() on mocked interfaceMockito - 在模拟界面上使用 doAnswer().when() 时出现 NullpointerException
【发布时间】:2021-04-03 12:49:27
【问题描述】:

我正在为我的 Android 模块编写 Junit 测试并在 RobolectricTestRunner 的帮助下运行它。我已经模拟了一个名为authenticationChannel1 的接口AuthenticationChannnel,并希望在调用authenticationChannel1 上的方法时对一些已定义的对象执行一些操作。我正在使用 Mockito 的 Mockito.doAnswer(..).when(authenticationChannel1).<method>() 功能。

我的@Before 方法中的方法的第一个存根运行良好。当我尝试在我的一个测试用例中存根另一种方法时,我得到NullPointerException。这是我的代码:

    @RunWith(RobolectricTestRunner.class)
    public class AuthenticationManagerTests {

    private AuthenticationManager authenticationManager;

    private final Context context = RuntimeEnvironment.application;

    private final WeakReference<Context> contextWeakReference = new WeakReference<>(context);

    @Mock(name="channel1")
    AuthenticationChannel authenticationChannel1;

    Event<AuthChannelEvent> authChannel1Event;

    @Mock(name="channel2")
    AuthenticationChannel authenticationChannel2;

    Event<AuthChannelEvent> authChannel2Event;


    private CountDownLatch countDownLatch;

    @Before
    public void before() {

        MockitoAnnotations.initMocks(this);

        authChannel1Event = new Event<>();
        authChannel2Event = new Event<>();

        // Setting up the event handlers for the channel 1
        Mockito.doAnswer(invocation -> {
            authChannel1Event.addEventHandler(invocation.getArgument(0));
            return null;
        })
                .when(authenticationChannel1) // This runs just fine ..
                .subscribeToAuthenticationChannelLifecycleEvent(any());

        // Setting up the event handlers for the channel 2
        Mockito.doAnswer(invocation -> {
            authChannel2Event.addEventHandler(invocation.getArgument(0));
            return null;
        })
                .when(authenticationChannel2)
                .subscribeToAuthenticationChannelLifecycleEvent(any());

        countDownLatch = new CountDownLatch(1);

        final List<AuthenticationChannel> authenticationChannels = new ArrayList<>();

        authenticationChannels.add(authenticationChannel1);
        authenticationChannels.add(authenticationChannel2);

        authenticationManager = // Initializing the object with the implementation to test ..

        // Setting up global mock methods ..
        Mockito.when(authenticationChannel1.getChannelIdentifier()).thenReturn("channel1");
        Mockito.when(authenticationChannel2.getChannelIdentifier()).thenReturn("channel2");


        // Mocking encryption key from authentication channels ...
        try {
            Mockito.when(authenticationChannel1.getEncryptionKeyElseAuthenticate(any(), any()))
                    .thenReturn(KeyUtil.getSecureRandomBytes(CryptoConstants.KEY_SIZE_128 / 8));
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        try {
            Mockito.when(authenticationChannel2.getEncryptionKeyElseAuthenticate(any(), any()))
                    .thenReturn(KeyUtil.getSecureRandomBytes(CryptoConstants.KEY_SIZE_128 / 8));
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }

        // Setting context to the authentication channels ..
        authenticationChannel1.setTriggeringContext(context);
        authenticationChannel2.setTriggeringContext(context);

    }

    @Test
    public void test_SetupChannel1() {

        final boolean[] setupSuccess = {false};

        Mockito.when(authenticationChannel1
                .isAuthenticationChannelUsagePossibleOnDevice()).thenReturn(true);
        Mockito.when(authenticationChannel2
                .isAuthenticationChannelUsagePossibleOnDevice()).thenReturn(true);

        // Setting up the event for authentication manager ...
        authenticationManager.subscribeToAuthenticationManagerLifecycleEvent(new Event.EventHandler<AuthManagerEvent>() {
            @Override
            public void run(AuthManagerEvent authManagerEvent) {
                if(authManagerEvent.getEventName().equals(AUTH_MANAGER_SETUP_SUCCESS)) {
                    setupSuccess[0] = true;
                }
                countDownLatch.countDown();
            }

            @Override
            public Executor getExecutor() {
                return MoreExecutors.directExecutor();
            }
        });

        // Intercepting the call of the setup method for the authentication channel ..
        Mockito.doAnswer(invocation -> {
            // Intercepting the call of calling of the authentication channel 1 and raising event
            // for success ...
            authChannel1Event.raiseEvent(new AuthChannelEvent(
                    CHANNEL_SETUP_SUCCESS,
                    "channel1",
                    new AuthPurpose(AuthenticationPurpose.
                            PURPOSE_SETUP,
                            "channel1",
                            false
                    )
            ));
            return null;
        })
                .when(authenticationChannel1) // Getting NullPoinnterException here ..
                .setUpAuthenticationChannel(any(), any());

                ..............
    }

}

有人能指出我在这里可能出错的地方吗?我浏览了一个有趣的帖子:https://medium.com/kenshoos-engineering-blog/mockito-gotcha-beware-of-method-invocation-when-stubbing-65cf5cf2e4af。虽然这与我的情况非常相似,但使用了另一种 Mockito 的模拟方式。

编辑 1 添加堆栈跟踪

java.lang.NullPointerException
    at in.zeta.apollo.encryptedstore.AuthenticationManagerTests.test_SetupChannel1(AuthenticationManagerTests.java:186)
    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:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:467)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:250)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:176)
    at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:49)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:142)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)


Process finished with exit code 255

【问题讨论】:

  • 你似乎没有实例化 authChannel1Event
  • @stultuske 我已经在之前的方法中做到了。
  • 请同时发布异常的完整堆栈跟踪。
  • @TimothyTruckle 完成。

标签: java android unit-testing mockito


【解决方案1】:

看起来你陷入了 Mockito 的特殊情况。

您使用更易读的存根形式(间距不同):

Mockito.when(
    authenticationChannel1
      .isAuthenticationChannelUsagePossibleOnDevice()
            ).thenReturn(true);
  

这里的问题是 Mockito 无法更改语句的执行顺序。因此,您的方法 isAuthenticationChannelUsagePossibleOnDevice 首先被执行,Mockito 在将结果传递给 Test under Test 之前替换结果。如果您的方法isAuthenticationChannelUsagePossibleOnDevice 访问authenticationChannel1 的任何成员变量,这反过来会导致NullÜointerException,因为Mockito 使用null 初始化它们。

你有两个选择:

  1. 将您的存根更改为 mockito 提供的第二种形式:

    Mockito.doReturn(true)
           .when(authenticationChannel1)     
           .isAuthenticationChannelUsagePossibleOnDevice();
    

使用这种形式时,不会调用依赖项中的原始方法。

  1. 不要使用mock,而是使用spy。在这种情况下,您从您的依赖项(包括其(过渡)依赖项)创建一个普通实例,并将其包装在一个间谍对象中。这意味着更多的工作,应该是最后的手段......

【讨论】:

  • authenticationChannel1 不是一个具体的类。它是一个接口的模拟。另外,我尝试将所有邀请更改为您提到的方式。还是不行。
  • 你能在这里提供进一步的帮助吗?我仍然坚持这个
  • @Swapnil 您的堆栈跟踪表明AuthenticationManagerTests.java 中的第 186 行是问题所在。那条特别的线是什么? (注意!从您的当前堆栈跟踪中获取行号!)
猜你喜欢
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多