等效的Answers.RETURNS_DEEP_STUBS为在mockito中的间谍

问题描述 投票:1回答:2

我一直无法找到一种方法来使用“Deep Stubs”来对Mockito中的间谍进行存根方法。我想要做的是这样的:

@Spy private Person person = //retrieve person

    @Test
    public void testStubbed() {
        doReturn("Neil").when(person).getName().getFirstName();
        assertEquals("Neil", person.getName().getFirstName());
    }

上面的代码编译时没有任何问题,但是在运行测试时,它无法说getName()无法返回返回类型(在本例中为Name类)。

通常,在嘲笑时,你必须使用 每个模拟对象的@Mock(answer = Answers.RETURNS_DEEP_STUBS)。然而,间谍似乎没有这样的东西。

有没有人成功地使用间谍成功进行深层嘲讽?

我收到的错误如下:

String cannot be returned by getName()
getName() should return Name

Due to the nature of the syntax above problem might occur because of:
1. Multithreaded testing 
//I'm not doing multithreaded testing
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies with doReturn|Throw() family of methods 
//As shown above, I'm already using the doReturn family of methods. 
java mocking mockito spy
2个回答
1
投票

虽然我仍然想知道是否有更好的方法来做到这一点,但我想为任何想要看的人发布一个解决方案。

下面的解决方案可以正常工作,要求您为每个级别的依赖项创建一个新的模拟(甚至是一个真实的对象/间谍)。换句话说,不是链接方法调用来创建存根,而是单独模拟每个级别。

@Spy private Person person = //retrieve person
@Mock private Name name;

@Test
public void testStubbed() {
    doReturn(name).when(person).getName();
    doReturn("Neil").when(name).getName();
    assertEquals("Neil", person.getName().getFirstName());
}

0
投票

你可以使用doAnswer(RETURNS_DEEP_STUBS)更接近你想要的深存根,但是你不能覆盖任意深度的方法调用而不用去处理它们的父调用。我会像你在答案中那样坚持使用手动单级深度模拟,或者尽可能使用更少的模拟。


间谍的默认行为是委托给它真正的方法调用,它通常会返回一个真实的对象(比如你的名字)而不是一个Mockito间谍。这意味着您通常无法使用Mockito更改这些对象的行为:间谍与被侦察的对象实际上不是同一个类,而是生成的子类,其中每个字段值都从间谍中复制 - 值。 (复制是一个重要的特性,因为委派间谍会对this有非常无意义的行为,包括方法调用和字段值。)

Foo foo = new Foo();
foo.intValue = 42;
foo.someObject= new SomeObject();

Foo fooSpy = Mockito.spy(foo);
// Now fooSpy.intValue is 42, fooSpy.someObject refers to the exact same
// SomeObject instance, and all of fooSpy's non-final methods are overridden to
// delegate to Mockito's behavior. Importantly, SomeObject is not a spy, and
// Mockito cannot override its behavior!

所以这不起作用:

doReturn("Neil").when(person).getName().getFirstName();
//   Mockito thinks this call ^^^^^^^^^ should return "Neil".

这也不会:

doReturn("Neil").when(person.getName()).getFirstName();
//    The object here ^^^^^^^^^^^^^^^^ won't be a mock, and even if Mockito
//    could automatically make it a mock, it's not clear whether that
//    should be the same spy instance every time or a new one every time.

在您的情况下,我会按照从最优先到最少的顺序选择以下内容:

  1. 创建一个真正的Name对象并使用doReturn安装它。看起来Name毕竟是一个数据对象(也就是值对象),这可能意味着它没有依赖关系,可靠的行为和难以模拟的状态转换。你可能没有通过嘲笑获得任何东西。
  2. 创建一个模拟名称并安装它as you do in your answer。如果Name比它看起来更复杂,或者它实际上并不存在,那么这尤其有用。
  3. 替换getName以返回深存根... doAnswer(RETURNS_DEEP_STUBS).when(person).getName(); ...然后你可以覆盖...... doReturn("Neil").when(person.getName()).getFirstName(); ......即使是任意深刻的价值观。 doReturn("Gaelic").when(person.getName() .getEtymology() .getFirstNameEtymology()) .getOrigin();

作为最后的社论,部分嘲笑的一个危害是,它很难说出哪种行为是真实的,哪种行为是伪造的;这可能会让您难以保证您正在测试的行为是prod行为而不是模拟行为。深层顽固的另一个危险是你可能违反了Law of Demeter的定义。如果您发现自己经常在测试中使用这种技术,那么可能需要考虑重新架构您的被测系统。

© www.soinside.com 2019 - 2024. All rights reserved.