【问题标题】:Equivalent of Answers.RETURNS_DEEP_STUBS for a spy in mockito等效于 mockito 中的间谍的 Answers.RETURNS_DEEP_STUBS
【发布时间】:2017-12-14 21:45:41
【问题描述】:

我一直无法找到一种方法来使用“深度存根”对 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)。但是,spy 似乎没有这样的东西。

有没有人使用间谍成功地进行过深度模拟?

我收到的错误如下:

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


    【解决方案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());
    }
    

    【讨论】:

    • 你应该从你的@Mock Name name中删除= new Name();无论如何,Mockito 都会在初始化时覆盖该值。
    • 你是对的,更新了。我写这篇文章时并没有太在意变量声明。
    【解决方案2】:

    您可以通过使用doAnswer(RETURNS_DEEP_STUBS) 更接近您想要的深度存根,但是您不能在不注意存根父调用的情况下覆盖任意深度的方法调用。我会像您在回答中那样坚持手动单级深度模拟,或者尽可能使用更少的模拟。


    间谍的默认行为是委托给它的真实方法调用,这通常会返回一个真实的对象(如您的姓名)而不是 Mockito 间谍。这意味着您通常无法使用 Mockito 更改这些对象的行为:spy 与被监视的对象实际上并不是同一个类,而是一个生成的子类,其中每个字段值都是从 spy 复制的-on 值。 (复制是一项重要功能,因为委托间谍对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();
      

    作为最后的社论,部分模拟的危险之一是很难分辨哪些行为是真实的,哪些是伪造的;这可能会让您难以保证您正在测试的行为是刺激行为而不是模拟行为。深度存根的另一个危险是您可能违反了Law of Demeter 的定义。如果您发现自己在测试中经常使用这种技术,那么可能是时候考虑重新构建您的测试系统了。

    【讨论】:

      【解决方案3】:
      @Test
      public void listTypeTest() throws Exception {
          doCallRealMethod().when(listType).setRecordCount(new BigInteger("556756756756"));
          listType.setRecordCount(new BigInteger("556756756756"));
          doCallRealMethod().when(listType).getRecordCount();
          assertEquals(new BigInteger("556756756756"), listType.getRecordCount());
      }
      

      【讨论】:

      • 你能详细说明一下这个答案吗?看起来与原始问题没有任何关系。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多