有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

mockitojava中的单元测试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
    }
}

如何防止调用超级用户。doSomethingParent()在我的方法中?(我正在编写测试的方法)


共 (3) 个答案

  1. # 1 楼答案

    我也遇到了类似的问题,所以我发现使用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();
        //...
      }
    }
    

    这种方法适合我

  2. # 2 楼答案

    我找到了另一种方法,这在我的案例中非常有用

    在我的例子中,我需要创建一个扩展另一个类的新类,其中包括一个非常复杂的(遗留代码)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,在那里提供一个模拟,并且只从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并摆脱继承

  3. # 3 楼答案

    使用这种类结构,模拟和测试是非常困难的。如果可能的话,我建议更改结构,因为在mist情况下,很难模拟和测试的类结构同样很难扩展和维护

    因此,如果您可以将您的类结构更改为类似于:

    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。间谍而不是莫基托。模拟特定于存根的方法调用,但使用真实对象

    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中断该方法调用。我不知道是否有办法将呼叫存根到super。如果可能的话(就像您没有重写类中的父方法一样),只需ommit关键字即可