【问题标题】:Mockito: Trying to spy on method is calling the original methodMockito:试图监视方法正在调用原始方法
【发布时间】:2012-07-22 03:15:33
【问题描述】:

我正在使用 Mockito 1.9.0。我想在 JUnit 测试中模拟一个类的单个方法的行为,所以我有

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

问题是,在第二行中,myClassSpy.method1() 实际上被调用,导致异常。我使用模拟的唯一原因是以后,每当调用 myClassSpy.method1() 时,将不会调用真正的方法并且将返回 myResults 对象。

MyClass 是一个接口,myInstance 是一个实现,如果这很重要的话。

我需要做些什么来纠正这种间谍行为?

【问题讨论】:

标签: java junit mockito


【解决方案1】:

让我引用the official documentation

关于监视真实物体的重要问题!

有时不可能使用 when(Object) 来存根间谍。示例:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

在你的情况下,它是这样的:

doReturn(resultsIWant).when(myClassSpy).method1();

【讨论】:

  • 如果我使用这种方法而我原来的方法仍然被调用怎么办?我传递的参数会不会有问题?这是整个测试:pastebin.com/ZieY790Psend 方法正在被调用
  • @EvgeniPetrov 如果您的原始方法仍在被调用,那可能是因为您的原始方法是最终方法。 Mockito 不模拟 final 方法,也不能警告你关于 final 方法的模拟。
  • 是的,不幸的是静态方法是不可模拟的和“不可窥探的”。我处理静态方法的方法是在静态调用周围包装一个方法,并在该方法上使用 doNothing 或 doReturn。使用单例或 scala 对象,我将逻辑的核心移至抽象类,这使我能够拥有可以创建间谍的对象的替代测试类 impl。
  • 如果 NOT final 和 NOT static 方法仍然被调用怎么办?
  • 对于所有已经到了这一点的人,尝试了一切,Mokito 仍然调用原始方法 - 请在下面查看 @ejaenv 答案。
【解决方案2】:

在我的例子中,使用 Mockito 2.0,我必须将所有 any() 参数更改为 nullable() 以存根真正的调用。

【讨论】:

  • 不要让 321 个投票的最佳答案让您失望,这解决了我的问题 :) 我已经为此苦苦挣扎了几个小时!
  • 这就是我的答案。为了让那些在模拟您的方法时更容易遵循的语法是:foo = Mockito.spy(foo);Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
  • 使用 Mockito 2.23.4 我可以确认这不是必需的,它适用于 anyeq 匹配器。
  • 在 2.23.4 lib 版本上尝试了三种不同的方法:any()、eq() 和 nullable()。只有后来的工作
  • 我正在使用 mockito-core 版本 3.4.0 并使其 nullable() 工作。有人可以解释为什么any() 不起作用吗?
【解决方案3】:

我的情况与接受的答案不同。我试图模拟一个不在该包中的实例的包私有方法

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

和测试类

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

编译是正确的,但是当它尝试设置测试时,它会调用真实的方法。

声明方法 protectedpublic 可以解决问题,但这不是一个干净的解决方案。

【讨论】:

  • 我遇到了类似的问题,但是 test 和 package-private 方法在同一个包中。我认为 Mockito 通常在包私有方法方面存在问题。
  • 现在有什么已知的解决办法吗?我面临与@Maragues 类似的情况。
【解决方案4】:

Tomasz Nurkiewicz 的回答似乎并不能说明全部情况!

NB Mockito 版本:1.10.19。

我是一个 Mockito 新手,所以无法解释以下行为:如果有专家可以改进这个答案,请随意。

这里提到的方法getContentStringValueNOT finalNOT static

这一行确实调用了原方法getContentStringValue

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

这行调用原方法getContentStringValue

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

由于我无法回答的原因,使用isA() 会导致doReturn 的预期(?)“不调用方法”行为失败。

我们来看看这里涉及到的方法签名:它们都是Matchersstatic方法。 Javadoc 都说两者都返回null,这本身就有点难以理解。可能会检查作为参数传递的Class 对象,但结果要么从未计算过,要么被丢弃。鉴于null 可以代表任何类并且您希望不调用模拟方法,isA( ... )any( ... ) 的签名不能只返回null 而不是通用参数* @987654338 @?

无论如何:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

API 文档对此没有提供任何线索。似乎也说需要这种“不调用方法”行为是“非常罕见的”。我个人一直使用这种技术:通常我发现模拟涉及“设置场景”的几行......然后调用一个方法,然后在模拟中“播放”场景你已经上演的背景......当你设置场景和道具时,你最不想要的就是让演员从左边进入舞台并开始表演他们的心......

但这远远超出了我的工资等级...我请任何路过的 Mockito 大祭司解释...

* 是“通用参数”的正确术语吗?

【讨论】:

  • 我不知道这是否会增加清晰度或进一步混淆问题,但 isA() 和 any() 之间的区别在于 isA 实际上进行类型检查,而 any() 系列方法是创建只是为了避免参数的类型转换。
  • @KevinWelker 谢谢。事实上,方法名称并不缺乏某种不言自明的质量。然而,我对天才的 Mockito 设计师提出质疑,尽管如此温和,因为他们没有充分记录。毫无疑问,我还需要阅读另一本关于 Mockito 的书。 PS实际上似乎很少有资源可以教“中级Mockito”!
  • 历史是 anyXX 方法最初是作为处理类型转换的一种方式创建的。然后当有人建议他们添加参数检查时,他们不想破坏现有 API 的用户,所以他们创建了 isA() 系列。知道 any() 方法应该一直进行类型检查,他们推迟了更改这些,直到他们在 Mockito 2.X 大修中引入了其他重大更改(我还没有尝试过)。在 2.x+ 中,anyX() 方法是 isA() 方法的别名。
  • 谢谢。对于我们这些同时进行多个库更新的人来说,这是一个关键的答案,因为过去运行的代码会突然而无声地失败。
【解决方案5】:

另一种可能导致间谍问题的情况是,当您正在测试 spring beans(使用 spring 测试框架)或其他一些 在测试期间代理您的对象的框架强>。

例子

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

在上面的代码中,Spring 和 Mockito 都会尝试代理您的 MonitoringDocumentsRepository 对象,但 Spring 将是第一个,这将导致 findMonitoringDocuments 方法的真正调用。如果我们在存储库对象上放置一个间谍后调试我们的代码,它在调试器中将如下所示:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean 救援

如果我们使用@Autowired 注释而不是@SpyBean 注释,我们将解决上述问题,SpyBean 注释也会注入存储库对象,但它会首先由 Mockito 代理,并且在调试器中看起来像这样

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

这里是代码:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

【讨论】:

  • @SpyBean 仅在 Spring Boot 中可用:Spring 是否有类似的解决方法?
【解决方案6】:

我又找到了 spy 调用原始方法的另一个原因。

有人想mock一个final类,发现了MockMaker

由于这与我们当前的机制不同,并且这个机制有不同的限制,并且由于我们想要收集经验和用户反馈,因此必须明确激活此功能才能使用;它可以通过 mockito 扩展机制通过创建包含单行的文件 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 来完成:mock-maker-inline

来源:https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

在我合并该文件并将其带到我的机器后,我的测试失败了。

我只需要删除该行(或文件),spy() 就可以了。

【讨论】:

  • 这就是我的原因,我试图模拟一个最终方法,但它一直在调用真实的方法,而没有明确的错误消息令人困惑。
【解决方案7】:

监视真实物体的重要注意事项

当使用间谍 stub 方法时,请使用 doReturn() 系列方法。

when(Object) 将导致调用可能引发异常的实际方法。

List spy = spy(new LinkedList());

//Incorrect , spy.get() will throw IndexOutOfBoundsException   
 when(spy.get(0)).thenReturn("foo");

//You have to use doReturn() for stubbing    
doReturn("foo").when(spy).get(0);

【讨论】:

    【解决方案8】:

    确保不调用类中的方法的一种方法是使用虚拟对象覆盖该方法。

        WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
                @Override
                public void select(TreeItem i) {
                    log.debug("SELECT");
                };
            });
    

    【讨论】:

      【解决方案9】:

      正如在一些 cmets 中提到的,我的方法是“静态的”(尽管被类的实例调用)

      public class A {
        static void myMethod() {...}
      }
      A instance = spy(new A());
      verify(instance).myMethod(); // still calls the original method because it's static
      

      解决方法是创建一个实例方法或将 Mockito 升级到具有一些配置的较新版本:https://stackoverflow.com/a/62860455/32453

      【讨论】:

        【解决方案10】:

        聚会晚了,但上述解决方案对我不起作用,所以分享我的 0.02$

        Mokcito 版本:1.10.19

        MyClass.java

        private int handleAction(List<String> argList, String action)
        

        Test.java

        MyClass spy = PowerMockito.spy(new MyClass());
        

        以下对我不起作用(正在调用实际方法):

        1.

        doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());
        

        2.

        doReturn(0).when(spy , "handleAction", any(), anyString());
        

        3。

        doReturn(0).when(spy , "handleAction", null, null);
        

        以下工作:

        doReturn(0).when(spy , "handleAction", any(List.class), anyString());
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-26
          • 1970-01-01
          • 2013-08-29
          相关资源
          最近更新 更多