Mockito的spy方法无效

6

我希望你能帮助翻译IT技术相关内容,以下是需要翻译的内容:

我在mockito.spy方法中遇到了麻烦。

最近我加入了一个“老”项目,我的第一个任务是在其中添加mockito,并进行真正的单元测试 :)

该项目存在许多概念上的问题,但这不是重点 ;)

我来解释一下我的问题:

我有一个类

public class Tutu{
  public Tutu(){
  }
}

public class Toto{
  public Toto(){
  }
  public int executeToto(Tutu tutu){
    //do some stuff
    return 5;
  }
}

public class Titi{
  private Toto toto;

  public Titi(){
     this.toto = new Toto();     
  }

  public void executeTiti(){
      //do some stuff
      Tutu tutu = new Tutu();
      int ret = this.toto.executeToto(tutu);
      //do some stuff
  }
}

在我的测试类TitiTest.java中,我只想测试executeTiti方法,不想测试executeToto的内容,因为这个类有自己的测试类TotoTest.java。
但是你可以看到,toto在titi构造函数中被实例化,所以我尝试了以下代码: (我在我的测试中也使用了PowerMock,所以我正在使用PowerMockRunner,但似乎不是问题的原因)
@RunWith(PowerMockRunner.class)
public class TitiTest {

 @Test
 public void testExecuteTiti(){
   Toto toto = Mockito.spy(new Toto());
   Mockito.doReturn(2).when(toto).executeToto(Mockito.any(Tutu.class));

   Titi testedObject = new Titi();
   testedObject.executeTiti();
 }
}

但是真正的方法总是每次调用并且ret = 5 :(
我错过了什么吗?我在stackoverflow上阅读了许多帖子,并尝试了所有解决方案,但从未奏效,因为我认为我做得很正确。
我使用junit4.11/powermock1.5.4/mockito1.9.5
3个回答

15
Toto toto = Mockito.spy(new Toto());

请记住,这个代码会对你在这一行创建的Toto实例进行间谍操作/桩操作,而不是每个新创建的Toto实例。因此,当你调用以下代码时:

Titi testedObject = new Titi();
testedObject.executeTiti();

构造函数 new Titi() 本身创建了一个未受Mockito影响的Toto实例,所以对this.toto.executeAction() 的调用将始终返回5。


由于你正在使用PowerMockito,你可以选择阻止Toto的构造函数

@RunWith(PowerMockRunner.class)
@PrepareForTest(Titi.class) // stub the calling class Titi, not Toto!
public class TitiTest {
  @Test public void testExecuteTiti() {
    Toto toto = Mockito.spy(new Toto());
    Mockito.doReturn(2).when(toto).executeToto(Mockito.any(Tutu.class));

    PowerMockito.whenNew(Toto.class).withAnyArguments().thenReturn(toto);

    Titi testedObject = new Titi();
    testedObject.executeTiti();
  }
}

但我最喜欢的选项是为Titi创建一个次要构造函数,用于测试:

public Titi(){
  this.toto = new Toto();     
}

/** For testing only. Uses the passed Toto instance instead of a new one. */
Titi(Toto toto){
  this.toto = toto;
}

然后你只需要像这样调整你的测试:

@Test public void testExecuteTiti(){
  Toto toto = Mockito.spy(new Toto());
  Mockito.doReturn(2).when(toto).executeToto(Mockito.any(Tutu.class));

  Titi testedObject = new Titi(toto);
  testedObject.executeTiti();
}

你好,感谢帮助。在我发布问题后,我最终改变了构造函数以传递正确的实例,并且它起作用了。我只是想知道是否还有其他解决方案,因为这是一个没有测试的旧代码,我不想触碰现有的代码,但我同意你的观点,在这种情况下我别无选择 :) 谢谢 - Mançaux Pierre-Alexandre
我认为快速重构遗留代码以改善测试是非常值得的,但你可以在不改变任何生产代码的情况下追求答案中的PowerMockito解决方案。不过,你可能需要调整PowerMockito代码;我的经验有限。祝好运! - Jeff Bowman

3
你似乎忽略了一件事,那就是你的Toto类间谍实际上从未被Titi类使用。针对你的情况,我会采取以下措施:
1) 重构Titi类,在构造函数中接受Toto作为依赖项。这样,你就可以轻松地使用任何Toto创建Titi(并在单元测试中使用模拟对象)。
2) 如果第一种选项不可行,你可以采取以下措施:
public class Titi{
  private Toto toto;

  public Titi(){
     this.toto = new Toto();     
  }

  public void executeTiti(){
      //do some stuff
      Tutu tutu = new Tutu();
      int ret = getToto().executeToto(tutu);
      //do some stuff
  }

  //package private - used for overriding via spying 
  Toto getToto() {
      return toto;
  }
}

@RunWith(MockitoJUnitRunner.class)
public class TitiTest {

 @Test
 public void testExecuteTiti(){
   Toto toto = Mockito.mock(Toto.class);
   when(toto.executeToto(Mockito.any(Tutu.class)).thenReturn(2);

   Titi testedObject = new Titi();
   testedObject = spy(testedObject);
   doReturn(toto).when(testedObject).getToto();

   testedObject.executeTiti();
 }
}

是的,就像我在之前的回答中所说的那样,我没有改变构造函数的选择。感谢您的帮助 :) - Mançaux Pierre-Alexandre

0

不要只是粘贴链接,如果你能在这里解释一下就更好了。 - yardstick17

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接