我正在尝试模拟外部API调用,但代码结构我不知道mockito是否会有所帮助。
我有一个简单控制器:
public class SimpleController extends Anothercontroller
{
@RequestMapping("/classA")
{
.......
String response = postCall(url, .....);
}
}
public class AnotherController
{
public String postCall (String url, ......)
{
//This is the apache library to make post calls
return WebUtil.post(......);
}
}
所以现在我需要模拟postCall,它是对外部服务的调用。
在这里我可以在两个地方嘲笑:
1) postCall()在SimpleController,然而,我不知道如何做到这一点,因为它倾向于继承比组合。
2) WebUtil.post(.....)然而,我不知道mockito如何模拟静态方法。
我不想重构代码结构,因为还有很多其他的代码依赖于它。
1) postCall()在SimpleController,但我不知道怎么做因为它更倾向于继承而不是组合。
这在Mockito使用间谍时是可能的。除非另有指定,否则间谍是使用真实方法的对象的模拟。
// spyController will use real methods expect for postCall()
SimpleController spyController = Mockito.spy(new SimpleController());
Mockito.doReturn("mockedString").when(spyController).postCall();
2) WebUtil.post(.....)然而,我不知道mockito如何可以模拟一个静态方法。
这在Mockito中是不可能的,但有两个解决方案:
- 使用PowerMock,它允许静态模拟。重构代码,使其不直接调用静态方法。@mdewit的回答中已经解释了这一点,所以我只让你阅读那里的细节。
我个人认为重构是最干净的解决方案,因为拥有静态依赖是邪恶的。如果出于某种原因,您不能或不想更改生产代码,那么Mockito.spy()
是一个很好的方法。
如果你被允许修改AnotherController,你可以这样做:首先你把WebUtil包装在另一个类中,像这样:
public class WebUtilWrapper {
String post(.....) {
return WebUtil.post(.....);
}
}
然后,向AnotherController添加一个构造函数,将WebUtilWrapper作为参数。这个构造函数将在单元测试中使用:
public class AnotherController {
private WebUtilWrapper wrapper;
public AnotherController() {
//default constructor
this(new WebUtilWrapper());
}
public AnotherController(WebUtilWrapper wrapper) {
this.wrapper = wrapper;
}
public String postCall (String url, ......) {
//This is the apache library to make post calls
return this.wrapper.post(......);
}
}
最后还要向您的SimpleController添加参数化构造函数。
public class SimpleController extends Anothercontroller {
public SimpleController() {
super();
}
public SimpleController(WebUtilWrapper wrapper) {
super(wrapper);
}
.
.
.
现在您可以在单元测试中模拟WebUtilWrapper(而不是WebUtil)。剩下的代码将正常工作,因为默认构造函数仍然可用。
在本例中,PowerMock是模拟WebUtil.post()
的合理选项。他们有一个使用Spring和@Rule
的例子,默认方式是使用@RunWith
,但在Spring中,您通常已经使用了该注释。