【问题标题】:Getting Mockito error: "Wanted but not invoked... actually, there were zero interactions with this mock"获取 Mockito 错误:“需要但未调用...实际上,与此模拟的交互为零”
【发布时间】:2020-04-14 15:08:52
【问题描述】:

我在为 Android 项目(在 Android Studio 中)中的 java 类运行单元测试时遇到上述错误。

被测类:

import android.content.Context;
import android.util.Log;

import **.CustomObject;
import java.util.concurrent.CountDownLatch;
import androidx.annotation.NonNull;

public class CustomClass {
  private static final String string = "a";
  private static CustomObject customObject = null;
  private static CountDownLatch initializedLatch = new CountDownLatch(1);

  @NonNull
  public static CustomObject1 getCustomObject1() {
      try {
          initializedLatch.await();
          assert customObject != null;
          return customObject;

      } catch (InterruptedException e) {
        throw new RuntimeException(".");
      }
  }

  public static void methodA(final Context context,
                                         final String string1,
                                         ) throws exception {
      initializedLatch.countDown();
  }


  public static void methodB(@NonNull final CustomObject customObjectInput) {
      customObject = customObjectInput;
  }
}

测试类:

import android.content.Context;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.util.concurrent.CountDownLatch;

import **.CustomObject;

import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class CustomClassTest{

    @Mock
    static CustomObject customObject;

    @Mock
    static Context context;

    @Mock
    CountDownLatch mCountDownLatch;

    @Mock
    CountDownLatch mInitializedLatch;

    @InjectMocks
    CustomClass customClass;

    @Before
    public void setUp() {
        customObject = Mockito.spy(CustomObject.class);
        context = Mockito.spy(Context.class);
    }

    @Test
    public void customClassTest() {

        doNothing().when(mInitializedLatch).countDown();

        CustomClass.methodB(customObject);
        try {
            CustomClass.methodA(context, "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        verify(mInitializedLatch).countDown();

        try {
            doNothing().when(mInitializedLatch).await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Class.getCustomObject();
    }

我在运行 customClassTest 时收到的具体消息:

Wanted but not invoked:
mInitializedLatch.countDown();
-> at CustomClassTest.methodA(CustomClassTest.java:79)
Actually, there were zero interactions with this mock.

Wanted but not invoked:
mInitializedLatch.countDown();
-> at CustomClassTest.methodA(CustomClassTest.java:79)
Actually, there were zero interactions with this mock.

在每个相关行使用断点运行调试器似乎表明测试运行良好(所有变量都在正确的点正确分配)直到 verify(mInitializedLatch).countDown();,当消息出现(并且代码停止运行)。

任何帮助表示赞赏,谢谢。

更新 #1:

更改代码以删除静态关键字:

import android.content.Context;
import android.util.Log;

import **.CustomObject;
import java.util.concurrent.CountDownLatch;
import androidx.annotation.NonNull;

public class CustomClass {
  private final String string = "a";
  private CustomObject customObject = null;
  private CountDownLatch initializedLatch = new CountDownLatch(1);

  @NonNull
  public CustomObject1 getCustomObject1() {
      try {
          initializedLatch.await();
          assert customObject != null;
          return customObject;

      } catch (InterruptedException e) {
        throw new RuntimeException(".");
      }
  }

  public void methodA(final Context context,
                                         final String string1,
                                         ) throws exception {
      initializedLatch.countDown();
  }


  public void methodB(@NonNull final CustomObject customObjectInput) {
      customObject = customObjectInput;
  }
}
import android.content.Context;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.util.concurrent.CountDownLatch;

import **.CustomObject;

import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public class CustomClassTest{

    @Mock
    CustomObject customObject;

    @Mock
    Context context;

    @Mock
    CountDownLatch mCountDownLatch;

    @Mock
    CountDownLatch mInitializedLatch;

    @InjectMocks
    CustomClass customClass;

    @Before
    public void setUp() {
        customObject = Mockito.spy(CustomObject.class);
        context = Mockito.spy(Context.class);
    }

    @Test
    public void customClassTest() {

        doNothing().when(mInitializedLatch).countDown();

        customClass.methodB(customObject);
        try {
            customClass.methodA(context, "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        verify(mInitializedLatch).countDown();

        try {
            doNothing().when(mInitializedLatch).await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        customClass.getCustomObject();
    }

现在读取错误消息:

error: non-static method methodA(Context,String) cannot be referenced from a static context
error: non-static method getCustomObject1() cannot be referenced from a static context

第二条错误消息显示六次。代码似乎没有编译。

【问题讨论】:

    标签: java android testing mockito


    【解决方案1】:

    在 CustomClass 中,CountDownLatch 被声明为静态字段并被初始化。如果你调试你的类,你可以看到 Mockito 没有模拟/代理这个字段。所有与 initializedLatch 对象的代码交互都不会被 Mockito 代理拦截,所以当你通过 doNothing().when(mInitializedLatch).countDown() 设置测试时,实际上你并没有将字段设置为 customClass。所以当你使用 verify(mInitializedLatch).countDown(),您实际上是在对 Mockito 说,您希望与此 mock 进行一次交互,但由于上述原因,没有进行交互。

    【讨论】:

      【解决方案2】:

      您没有得到任何调用,因为实际调用不是使用您模拟的 mInitializedLatch 对象进行的。

      在模拟任何对象时,您需要告诉编译器使用这个模拟对象而不是源实现中确实存在的对象。

      这可以通过将您要测试的对象作为实例变量并在构造函数中传递模拟对象来实现。
      然后将从您的模拟对象进行调用,并且 mockito 将能够跟踪这些。

      例子:

      // Source Code
      public class CustomerClass {
          private final CountDownLatch initializedLatch
      
          public CustomerClass(CountDownLatch initializedLatch) {
              this.initializedLatch = initializedLatch;
          }
      }
      

      现在,在您的代码中使用此实例变量,而不是您定义的静态变量。

      在测试代码中,通过传递模拟的 initializedLatch 对象来创建 CustomerClass 的构造函数,然后它将像魅力一样工作。

      如果您只想在那里初始化 initializedLatch 的值。您可以通过在我上面定义的构造函数旁边保留一个默认构造函数来做同样的事情。

      这个默认构造函数可以调用参数化构造函数。

      public CustomerClass() {
          this(new CountDownLatch(1));
      }
      

      编辑:

      您还需要更改源代码实现。

      import android.content.Context;
      import android.util.Log;
      
      import **.CustomObject;
      import java.util.concurrent.CountDownLatch;
      import androidx.annotation.NonNull;
      
      public class CustomClass {
        private static final String string = "a";
        private CustomObject customObject;
        private CountDownLatch initializedLatch;
      
        public CustomClass() {
          this(new CountDownLatch(1), null);
        }
      
        public CustomClass(CountDownLatch initializedLatch, CustomObject customObject) {
          this.initializedLatch = initializedLatch;
          this.customObject = customObject;
        }
      
        @NonNull
        public CustomObject1 getCustomObject1() {
            try {
                initializedLatch.await();
                assert customObject != null;
                return customObject;
      
            } catch (InterruptedException e) {
              throw new RuntimeException(".");
            }
        }
      
        public void methodA(final Context context final String string1) throws Exception {
            initializedLatch.countDown();
        }
      
      
        public void methodB(@NonNull final CustomObject customObjectInput) {
            customObject = customObjectInput;
        }
      }
      

      现在,上面的源代码实现将使用构造函数中提供的 initializedLatchcustomObject

      测试代码

      import android.content.Context;
      
      import org.junit.Before;
      import org.junit.runner.RunWith;
      import org.mockito.InjectMocks;
      import org.mockito.Mock;
      
      import java.util.concurrent.CountDownLatch;
      
      import **.CustomObject;
      
      import org.junit.Test;
      import org.mockito.Mockito;
      import org.mockito.MockitoAnnotations;
      import org.mockito.junit.MockitoJUnitRunner;
      
      import static org.mockito.Mockito.doNothing;
      import static org.mockito.Mockito.verify;
      
      @RunWith(MockitoJUnitRunner.class)
      public class CustomClassTest{
      
          @Mock
          CustomObject customObject;
      
          @Mock
          Context context;
      
          @Mock
          CountDownLatch mInitializedLatch;
      
          @InjectMocks
          CustomClass customClass;
      
          @Before
          public void setUp() {
              customClass = new CustomClass(mInitializedLatch, customObject);
          }
      
          @Test
          public void customClassTest() {
      
              doNothing().when(mInitializedLatch).countDown();
      
              customClass.methodB(customObject);
              try {
                  customClass.methodA(context, "");
              } catch (Exception e) {
                  e.printStackTrace();
              }
              verify(mInitializedLatch).countDown();
      
              try {
                  doNothing().when(mInitializedLatch).await();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              customClass.getCustomObject();
          }
      

      关于你得到的错误,我不认为那是因为 您从测试中拨打的电话。

      【讨论】:

      • 谢谢 - 我已经添加了构造函数,但现在它没有编译,因为我无法从静态上下文调用 initializedLatch.countDown()。我将方法更改为非静态,但由于 methodA 是非静态的,因此在测试类中弄乱了 CustomClass.methodA(context, ""),因此我将 CustomClass 的所有引用更改为 customClass。现在我仍然收到一条消息,说 methodA 和 getCustomObject1 不能从静态上下文中引用,但我不明白为什么会出现这种情况,因为除了 doNothing() 的导入之外没有使用 static 关键字并验证()。有什么想法吗?
      • 嗨。不好意思推迟了。我已将更新的代码和错误消息作为更新编辑,请参见上文。谢谢。
      • 认为我已经解决了这个问题。似乎我的项目中还有其他类正在引用这个仍然从静态上下文中引用它的类。将首先更新那些。
      • 我添加了可能对你有用的代码 sn-p。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-18
      相关资源
      最近更新 更多