Mockito:试图窥探方法是在调用原始方法


349

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

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

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

MyClass是一个接口,并且是该接口myInstance的实现(如果有关系的话)。

我需要怎么做才能纠正这种间谍行为?


Answers:


608

让我引用官方文件

从事间谍活动的重要陷阱!

有时,不可能使用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(resulstIWant).when(myClassSpy).method1();

26
如果我使用这种方法,而我原来的方法仍然被调用怎么办?我传递的参数可能有问题吗?这是整个测试:称为pastebin.com/ZieY790P send方法
Evgeni Petrov 2014年

25
@EvgeniPetrov如果仍在调用原始方法,则可能是因为原始方法是最终方法。Mockito不会模拟最终方法,也无法警告您有关最终方法的模拟。
MarcG 2014年

1
doThrow()也可能吗?
Gobliins

1
是的,不幸的是静态方法是不可模拟的且“不可监视的”。我要处理的静态方法是在静态调用周围包装一个方法,并对该方法使用doNothing或doReturn。使用单例或scala对象,我将逻辑的内容移到了抽象类上,这使我能够对可以创建监视对象的对象进行替代测试类的实现。
Andrew Norman

24
如果还不能调用NOT final和NOT static方法,该怎么办?
X-HuMan

27

我的情况与接受的答案不同。我正在尝试为不存在于该程序包中的实例模拟程序包私有方法

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

编译是正确的,但是在尝试设置测试时,它将调用real方法。

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


2
我遇到了类似的问题,但是test和package-private方法位于同一软件包中。我认为Mockito总体上可能会遇到包私有方法的问题。
戴夫

22

就我而言,使用Mockito 2.0,我必须将所有any()参数更改为nullable(),以便对真正的调用进行存根处理。


2
不要让那321个投票最高的答案使您失望,这解决了我的问题:)我已经为此努力了几个小时!
克里斯·凯塞尔

3
这就是我的答案。为使模拟方法时的后续操作更加容易,语法为: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

随着2.23.4的Mockito我可以证实,这是没有必要的,它正常工作与anyeq匹配器。
vmaldosan '19

2
在2.23.4 lib版本上尝试了三种不同的处理方法:any(),eq()和nullable()。只有后来的作品
ryzhman

嗨,您的解决方案非常好,也对我有用。谢谢
Dhiren Solanki

16

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失败。

让我们看一下这里涉及的方法签名:它们都是的static方法Matchers。Javadoc都说这两个都是return null,这很难让您自己动手。大概Class检查了作为参数传递的对象,但是结果从不计算或丢弃。鉴于它null可以代表任何类,并且您希望不调用模拟方法,因此不能签名,isA( ... )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大祭司讲解...

*“通用参数”是否正确?


我不知道这是否增加了清晰度或进一步混淆了这个问题,但是isA()和any()之间的区别在于isA实际上进行类型检查,而any()系列方法的创建只是为了避免对类型进行类型转换。论点。
凯文·韦尔克

@KevinWelker谢谢。实际上,方法名称并不缺乏某种不言自明的特征。但是,我的确对天才Mockito设计师提出了质疑,因为他们没有充分地记录下来。毫无疑问,我需要阅读有关Mockito的另一本书。PS实际上似乎很少有资源可以教“中级Mockito”!
麦克啮齿动物,

1
历史是,首先创建了anyXX方法,以作为仅处理类型转换的一种方法。然后,当有人建议他们添加参数检查时,他们不想破坏现有API的用户,因此他们创建了isA()系列。他们知道any()方法应该一直进行类型检查,于是他们推迟了更改,直到在Mockito 2.X大修中引入了其他重大更改(我还没有尝试过)。在2.x +中,anyX()方法是isA()方法的别名。
凯文·韦尔克

谢谢。对于我们那些同时进行多个库更新的人来说,这是一个关键性的答案,因为曾经运行的代码突然而无声地失败了。
Dex Stakker '18

5

可能导致间谍问题的另一种可能的情况是,您正在测试spring bean(使用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));
}

1

我发现间谍调用原始方法的另一个原因。

有人想到模拟一个final类,然后发现MockMaker

由于此功能与我们当前的机制不同,并且此机制具有不同的局限性,并且我们希望收集经验和用户反馈,因此必须明确激活此功能才能使用;通过创建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-classes方法

合并并将该文件带到计算机后,测试失败。

我只需要删除行(或文件)即可spy()


这就是我的原因,我试图模拟一种最终方法,但是它一直在调用真实方法,而没有清晰的错误消息,这很令人困惑。
巴沙尔·阿里·拉巴迪

1

参加聚会有点晚,但是上述解决方案对我不起作用,因此分享我的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());

0

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.