我在使用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
Toto toto = Mockito.spy(new Toto());
请记住,此监视/存根是您在这一行中创建的 Toto 实例,而不是每个新创建的 Toto。所以当你打电话时:
Titi testedObject = new Titi();
testedObject.executeTiti();
构造函数
new Titi()
本身会创建一个新的 Toto 实例,不受 Mockito 的影响,因此对 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();
}
您似乎忽略了这样一个事实:您的 Toto 类 Spy 从未真正被 Titi 类使用过。
对于你的情况我会做的是
1) 重构 Titi 类以接受 Toto 作为构造函数中的依赖项。这样您就可以轻松地使用任何 Toto 创建 Titi(并在单元测试中使用模拟)
2) 如果选项 1 不可行,您可以执行以下操作:
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();
}
}
这里有一篇文章描述了使用单行方法或工厂辅助方法来测试不注入协作者的类。 https://code.google.com/p/mockito/wiki/MockingObjectCreation
当您在 java 17 中遇到与 Spy 相关的问题时,其中 Spy 对象为空,那么我们需要为此类包传递 vm 参数,例如 --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/java.lang=ALL-UNNAMED --add-opens=java.desktop/java.util=ALL-UNNAMED 因为它尝试为 Spy 打开该包,但如果没有这个虚拟机参数,它就不允许。