Mockito:试图监视方法正在调用原始方法

Posted

技术标签:

【中文标题】Mockito:试图监视方法正在调用原始方法【英文标题】:Mockito: Trying to spy on method is calling the original method 【发布时间】: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 是一个实现,如果这很重要的话。

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

【问题讨论】:

看看这个:***.com/a/29394497/355438 【参考方案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://***.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());

【讨论】:

以上是关于Mockito:试图监视方法正在调用原始方法的主要内容,如果未能解决你的问题,请参考以下文章

Android单元测试系列-Mock之Mockito

如何断言使用 mockito 调用方法

使用 Mockito 从模拟中抛出已检查的异常

Mockito验证只调用了一个预期的方法

Mockito - 验证对象根本没有被调用

Android:在JUnit中传递上下文并使用共享首选项