我有一个遗留类,它包含一个new()调用来实例化LoginContext():
public class TestedClass {
public LoginContext login(String user, String password) {
LoginContext lc = new LoginContext("login", callbackHandler);
}
}
我想使用Mockito来测试这个类来模拟LoginContext,因为它要求在实例化之前设置JAAS安全性东西,但是我不知道如何在不更改login()方法来外化LoginContext的情况下这样做。是否可以使用Mockito来模拟LoginContext类?
答案 0 :(得分:47)
对于未来,我建议Eran Harel's answer(重构移动new
到可以模拟的工厂)。但是,如果您不想更改原始源代码,请使用非常方便且独特的功能:间谍。来自documentation:
您可以创建真实对象的间谍。当你使用间谍时,会调用真正的方法(除非方法被存根)。
真正的间谍应该谨慎使用,例如在处理遗留代码时。
在你的情况下你应该写:
TestedClass tc = spy(new TestedClass());
LoginContext lcMock = mock(LoginContext.class);
when(tc.login(anyString(), anyString())).thenReturn(lcMock);
答案 1 :(得分:28)
我全都是Eran Harel的解决方案,在不可能的情况下,Tomasz Nurkiewicz关于间谍活动的建议非常好。但是,值得注意的是,在某些情况下两者都不适用。例如。如果login
方法有点"更强大":
public class TestedClass {
public LoginContext login(String user, String password) {
LoginContext lc = new LoginContext("login", callbackHandler);
lc.doThis();
lc.doThat();
}
}
...这是旧代码,无法重构以将新LoginContext
的初始化提取到它自己的方法并应用上述解决方案之一。
为了完整性'为了清楚,值得一提的是第三种技术 - 在调用new
运算符时使用PowerMock注入模拟对象。但PowerMock并不是一颗银弹。它的工作原理是对它所模拟的类应用字节码操作,如果被测试的类使用字节代码操作或反射,并且至少根据我的个人经验,已经知道会给测试带来性能损失,这可能是狡猾的做法。然后,如果没有其他选项,唯一的选择必须是好的选择:
@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {
@Test
public void testLogin() {
LoginContext lcMock = mock(LoginContext.class);
whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
TestedClass tc = new TestedClass();
tc.login ("something", "something else");
// test the login's logic
}
}
答案 2 :(得分:21)
您可以使用工厂创建登录上下文。然后你可以嘲笑工厂并返回你想要的任何测试。
public class TestedClass {
private final LoginContextFactory loginContextFactory;
public TestedClass(final LoginContextFactory loginContextFactory) {
this.loginContextFactory = loginContextFactory;
}
public LoginContext login(String user, String password) {
LoginContext lc = loginContextFactory.createLoginContext();
}
}
public interface LoginContextFactory {
public LoginContext createLoginContext();
}
答案 3 :(得分:4)
public class TestedClass {
public LoginContext login(String user, String password) {
LoginContext lc = new LoginContext("login", callbackHandler);
lc.doThis();
lc.doThat();
}
}
- 测试类:
@RunWith(PowerMockRunner.class)
@PrepareForTest(TestedClass.class)
public class TestedClassTest {
@Test
public void testLogin() {
LoginContext lcMock = mock(LoginContext.class);
whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock);
//comment: this is giving mock object ( lcMock )
TestedClass tc = new TestedClass();
tc.login ("something", "something else"); /// testing this method.
// test the login's logic
}
}
从testLogin(){调用实际方法tc.login ("something", "something else");
时{
- 此LoginContext lc设置为null并在调用lc.doThis();
答案 4 :(得分:2)
不是我知道的,但是当您创建要测试的TestedClass实例时,如何做这样的事情:
TestedClass toTest = new TestedClass() {
public LoginContext login(String user, String password) {
//return mocked LoginContext
}
};
另一种选择是使用Mockito创建一个TestedClass实例,让模拟实例返回一个LoginContext。
答案 5 :(得分:1)
在可以修改被测试类以及何时需要避免字节代码操作,保持速度快或最小化第三方依赖性的情况下,我采用工厂来提取{{1操作。
new
有了这个,你可以轻松地对Pojo对象进行测试,断言和验证调用:
public class TestedClass {
interface PojoFactory { Pojo getNewPojo(); }
private final PojoFactory factory;
/** For use in production - nothing needs to change. */
public TestedClass() {
this.factory = new PojoFactory() {
@Override
public Pojo getNewPojo() {
return new Pojo();
}
};
}
/** For use in testing - provide a pojo factory. */
public TestedClass(PojoFactory factory) {
this.factory = factory;
}
public void doSomething() {
Pojo pojo = this.factory.getNewPojo();
anythingCouldHappen(pojo);
}
}
如果public void testSomething() {
Pojo testPojo = new Pojo();
TestedClass target = new TestedClass(new TestedClass.PojoFactory() {
@Override
public Pojo getNewPojo() {
return testPojo;
}
});
target.doSomething();
assertThat(testPojo.isLifeStillBeautiful(), is(true));
}
有多个构造函数,您必须使用额外参数进行复制,则可能会出现此方法的唯一缺点。
出于SOLID的原因,您可能希望将PojoFactory接口改为Pojo类,以及生产工厂。
TestClass
答案 6 :(得分:1)
我碰巧处于一种特殊的情况下,我的用例类似于Mureinik中的一个,但是我最终使用了Tomasz Nurkiewicz的解决方案。
方法如下:
class TestedClass extends AARRGGHH {
public LoginContext login(String user, String password) {
LoginContext lc = new LoginContext("login", callbackHandler);
lc.doThis();
lc.doThat();
return lc;
}
}
现在,PowerMockRunner
无法初始化TestedClass
,因为它扩展了AARRGGHH
,这反过来又进行了更多的上下文初始化...您将看到此路径将我引向何方:我将需要在几层上进行模拟。显然有巨大的气味。
我发现一个TestedClass
重构最少的nice hack:我创建了一个小方法
LoginContext initLoginContext(String login, CallbackHandler callbackHandler) {
new lc = new LoginContext(login, callbackHandler);
}
此方法的范围必须为package
。
然后您的测试存根将如下所示:
LoginContext lcMock = mock(LoginContext.class)
TestedClass testClass = spy(new TestedClass(withAllNeededArgs))
doReturn(lcMock)
.when(testClass)
.initLoginContext("login", callbackHandler)
技巧就完成了...