Mockito:试图监视方法就是调用原始方法

Mockito: Trying to spy on method is calling the original method

提问人:Dave 提问时间:7/24/2012 最后编辑:adam_0Dave 更新时间:11/30/2021 访问量:363329

问:

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

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

问题是,在第二行中,实际上被调用,导致异常。我使用 mocks 的唯一原因是,以后无论何时调用,都不会调用真正的方法,而是返回对象。myClassSpy.method1()myClassSpy.method1()myResults

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

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

Java junit mockito

评论

1赞 Ilya Serbis 5/25/2019
看看这个: stackoverflow.com/a/29394497/355438

答:

831赞 Tomasz Nurkiewicz 7/24/2012 #1

让我引用官方文档

间谍真实物体的重要陷阱!

有时无法使用 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();

评论

42赞 Evgeni Petrov 8/21/2014
如果我使用这种方法,而我原来的方法仍然被调用怎么办?我传递的参数有问题吗?这是整个测试:正在调用 pastebin.com/ZieY790P 方法send
36赞 Marcelo Glasberg 12/9/2014
@EvgeniPetrov,如果你的原始方法仍在被调用,那可能是因为你的原始方法是最终的。Mockito 不会嘲笑最终方法,也不能警告你关于最终方法的嘲笑。
2赞 Andrew Norman 10/14/2016
是的,不幸的是,静态方法是不可嘲笑的和“不可监视的”。我处理静态方法所做的是将方法包装在静态调用周围,并在该方法上使用 doNothing 或 doReturn。对于单例或标度对象,我将逻辑的实质移动到抽象类中,这使我能够拥有一个替代的测试类,我可以在其上创建监视对象。
50赞 X-HuMan 10/27/2016
如果 NOT final 和 NOT static 方法仍然被调用怎么办?
3赞 Timur Sadykov 10/2/2020
对于所有达到这一点的人,尝试了所有方法,Mokito 仍然调用原始方法 - 请看下面的答案@ejaenv。
30赞 Maragues 8/2/2016 #2

我的情况与公认的答案不同。我试图模拟一个包私有方法,用于不存在于该包中的实例

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(){}
}

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

声明受保护公开的方法可以解决问题,但这不是一个干净的解决方案。

评论

2赞 Dave 5/11/2017
我遇到了类似的问题,但测试和包私有方法在同一个包中。我认为也许 Mockito 在包私有方法方面普遍存在问题。
0赞 Subhajit 9/3/2021
现在有什么已知的修复方法吗?我面临着与@Maragues类似的情况。
22赞 mike rodent 11/7/2016 #3

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

NB Mockito 版本:1.10.19。

我非常喜欢 Mockito 新手,所以无法解释以下行为:如果有专家可以改进这个答案,请随时。

这里讨论的方法 是 NOTNOTgetContentStringValuefinalstatic

此行调用原始方法:getContentStringValue

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

此行调用原始方法:getContentStringValue

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

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

我们来看一下这里涉及的方法签名:它们都是 的方法。两者都被 Javadoc 说要返回,这本身有点难以理解。据推测,作为参数传递的对象被检查,但结果要么从未计算过,要么被丢弃。鉴于它可以代表任何类,并且您希望不调用模拟方法,那么 和 的签名不能返回而不是泛型参数* 吗?staticMatchersnullClassnullisA( ... )any( ... )null<T>

无论如何:

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

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

API 文档没有提供任何关于这一点的线索。它似乎还说,对这种“不调用方法”行为的需求是“非常罕见的”。就我个人而言,我一直在使用这种技巧:通常我发现嘲笑涉及几句“设置场景”的台词......然后调用一个方法,然后在您已上演的模拟上下文中“播放”场景......当你在设置布景和道具时,你最不想看到的就是演员们进入舞台,开始表演他们的心声......

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

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

评论

1赞 Kevin Welker 11/17/2016
我不知道这是否增加了清晰度或进一步混淆了问题,但 isA() 和 any() 之间的区别在于 isA 实际上执行类型检查,而 any() 系列方法的创建只是为了避免参数的类型转换。
0赞 mike rodent 11/21/2016
@KevinWelker 谢谢。事实上,方法名称并不缺乏某种不言自明的品质。然而,我确实对天才的 Mockito 设计师没有充分记录提出异议,无论多么温和。毫无疑问,我需要再读一本关于Mockito的书。PS 实际上,教授“中级 Mockito”的资源似乎很少!
1赞 Kevin Welker 11/24/2016
历史是,anyXX 方法最初是作为处理类型转换的一种方式而创建的。然后,当有人建议他们添加参数检查时,他们不想破坏现有 API 的用户,因此他们创建了 isA() 系列。知道 any() 方法应该一直进行类型检查,他们推迟了更改这些方法,直到他们在 Mockito 2.X 大修中引入了其他重大更改(我还没有尝试过)。在 2.x+ 中,anyX() 方法是 isA() 方法的别名。
0赞 Dex Stakker 7/21/2018
谢谢。对于我们这些同时进行多个库更新的人来说,这是一个关键的答案,因为过去运行的代码突然悄无声息地失败了。
59赞 ejaenv 3/30/2017 #4

就我而言,使用 Mockito 2.0,我必须将所有参数更改为 才能存根实际调用。any()nullable()

评论

12赞 Chris Kessel 11/21/2017
不要让 321 票的最高答案让你失望,这解决了我的问题:)我已经为此苦苦挣扎了几个小时!
12赞 Stryder 8/24/2018
这就是我的答案。为了让那些在模拟你的方法时更容易遵循它,语法是:foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
3赞 ryzhman 4/9/2019
在 2.23.4 lib 版本上尝试了三种不同的方法:any()、eq() 和 nullable()。只有后者才有效
3赞 Boss Man 7/21/2020
我使用 mockito-core 版本 3.4.0 并使其正常工作。有人可以解释为什么不起作用吗?nullable()any()
4赞 Wesley De Keirsmaeker 10/20/2021
但是在这篇文章的情况下,没有参数
3赞 Matruskan 1/17/2018 #5

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

有人想嘲笑一堂课,并发现:finalMockMaker

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

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

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

我只需要删除该行(或文件),然后工作。spy()

评论

0赞 Bashar Ali Labadi 4/15/2019
这就是我的情况,我试图嘲笑最终方法,但它一直在调用真实的方法,而没有明确的错误消息,这令人困惑。
0赞 Geoffrey Ritchey 6/20/2018 #6

确保不调用类中的一种方法是用虚拟方法重写该方法。

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });
19赞 Adrian Kapuscinski 10/8/2018 #7

另一种可能导致间谍问题的情况是,当您测试 Spring Bean(使用 Spring Test 框架)或在测试期间代理对象的其他框架时。

@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救援

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

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

代码如下:

@SpyBean
private MonitoringDocumentsRepository repository

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

评论

1赞 Marco Lackovic 9/24/2020
@SpyBean仅在 Spring Boot 中可用:Spring 是否有类似的解决方法?
-1赞 tryingToLearn 9/13/2019 #8

派对有点晚了,但上述解决方案对我不起作用,所以分享我的 0.02 美元

Mokcito 版本:1.10.19

我的类:.java

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

测试.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);

以下 WORKED:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());
0赞 rogerdpack 10/5/2021 #9

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

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

12赞 Rakesh Soni 11/30/2021 #10

关于监视真实物体的重要陷阱

当使用 spies 存根方法时,请使用 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);

评论

0赞 Mostafa Imani 11/13/2022
toString() 不能返回布尔值 toString() 应该返回 String