单元测试- Mock Java中的Mock继承方法



我的类结构如下:

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
    }
}

乍一看,似乎只是为了使代码可测试而添加了一些样板代码。然而,我也可以把它看作是代码实际应该是什么样子的一个指示器。也许有一天,有人(谁会找到勇气和预算;)可以重构代码,例如,用doComplexStuffParent的逻辑实现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/

相关内容

  • 没有找到相关文章

最新更新