我的类结构如下:
public class MyParentClass {
void doSomethingParent() {
System.out.println("something in parent");
}
}
public class MyClass extends MyParentClass {
protected String createDummyRequest(Holder myHolder) {
//...
super.doSomethingParent();//I want to avoid this
//...
callingDB();
return "processedOutput";
}
private void callingDB() {
System.out.println("Calling to DB");
}
}
然后是单元测试:
public class UnitTest {
public void testCreateDummyRequest() {
//create my mock holder
Holder mockHolder = new Holder();
MyClass mockObj = Mockito.mock(MyClass.class);
//mock doSomethingParent()
//mock callingDB()
//as mockObj is a fully mock, but I need to run my real method
//Mockito.when(mockObj.createDummyRequest(mockHolder)).thenCallRealMethod();
mockObj.createDummyRequest(mockHolder);
//Problem: doSomethingParent() is getting called though I have mocked it
}
}
如何防止在我的方法中调用super.doSomethingParent() ?(我正在编写测试的方法)
对于这种类结构,模拟和测试是非常困难的。如果可能的话,我建议更改结构,因为在某些情况下,难以模拟和测试的类结构同样难以扩展和维护。
所以如果你可以把你的类结构改成类似的:
public class MyClass {
private DoSomethingProvider doSomethingProvider;
private DbConnector dbConnector;
public MyClass (DoSomethingProvider p, DbConnector c) {
doSomethingProvicer = p;
dbConnector = c;
}
protected String createDummyRequest(Holder myHolder){
//...
doSomethingProvider.doSomethingParent();
//...
dbConnector.callingDB();
return "processedOutput";
}
}
然后你可以很容易地用DoSomethingProvider和DbConnector的模拟创建你的实例,瞧....
如果你不能改变你的类结构,你需要使用Mockito。间谍而不是Mockito。模拟存根特定的方法调用,但使用真实对象。
public void testCreateDummyRequest(){
//create my mock holder
Holder mockHolder = new Holder();
MyClass mockObj = Mockito.spy(new MyClass());
Mockito.doNothing().when(mockObj).doSomething();
mockObj.createDummyRequest(mockHolder);
}
注意:使用super
关键字可以防止Mockito存根该方法调用。我不知道是否有办法让呼叫存根。如果可能的话(比如你没有在你的类中重写父方法),只提交关键字。
我遇到了类似的问题,所以我发现使用spy()
可以帮助解决这个问题。
public class UnitTest {
private MyClass myObj;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
myObj= spy(new MyClass());
}
@Test
public void mockedSuperClassMethod(){
doNothing().when((MyParentClass )myObj).doSomethingParent();
//...
}
}
我发现了另一种方法,对我来说非常有用。
在这种情况下,我需要创建一个扩展另一个类的新类,其中包含一个非常复杂的(遗留代码)protected final
方法。由于复杂性,不可能真正重构使用组合,所以这是我想到的。
假设我有以下内容:
abstract class Parent {
public abstract void implementMe();
protected final void doComplexStuff( /* a long parameter list */) {
// very complex legacy logic
}
}
class MyNewClass extends Parent {
@Override
public void implementMe() {
// custom stuff
doComplexStuff(/* a long parameter list */); // calling the parent
// some more custom stuff
}
}
我是这样重新排列这段代码的:
abstract class Parent {
public abstract void implementMe();
protected final void doComplexStuff( /* a long parameter list */) {
// very complex legacy logic
}
}
interface ComplexStuffExecutor {
void executeComplexStuff(/* a long parameter list, matching the one from doComplexStuff */);
}
class MyNewClass extends Parent {
private final ComplexStuffExecutor complexStuffExecutor;
MyNewClass() {
this.complexStuffExecutor = this::doComplexStuff;
}
MyNewClass(ComplexStuffExecutor complexStuffExecutor) {
this.complexStuffExecutor = complexStuffExecutor;
}
@Override
public void implementMe() {
// custom stuff
complexStuffExecutor.doComplexStuff(/* a long parameter list */); // either calling the parent or the injected ComplexStuffExecutor
// some more custom stuff
}
}
为"生产"创建MyNewClass
实例时;
ComplexStuffExecutor
,在那里提供一个mock,并且只测试MyNewClass
的自定义逻辑,即:
class MyNewClassTest {
@Test
void testImplementMe() {
ComplexStuffExecutor complexStuffExecutor = Mockito.mock(ComplexStuffExecutor.class);
doNothing().when(complexStuffExecutor).executeComplexStuff(/* expected parameters */);
MyNewClass systemUnderTest = new MyNewClass(complexStuffExecutor);
// perform tests
}
}
乍一看,似乎只是为了使代码可测试而添加了一些样板代码。然而,我也可以把它看作是代码实际应该是什么样子的一个指示器。也许有一天,有人(谁会找到勇气和预算;)可以重构代码,例如,用doComplexStuff
和Parent
的逻辑实现ComplexStuffExecutor
,将其注入MyNewClass
,并摆脱继承。
可以这样做
public class BaseController {
public void method() {
validate(); // I don't want to run this!
}
}
public class JDrivenController extends BaseController {
public void method(){
super.method()
load(); // I only want to test this!
}
}
@Test
public void testSave() {
JDrivenController spy = Mockito.spy(new JDrivenController());
// Prevent/stub logic in super.method()
Mockito.doNothing().when((BaseController)spy).validate();
// When
spy.method();
// Then
verify(spy).load();
}
来源:https://blog.jdriven.com/2013/05/mock-superclass-method-with-mockito/