Java Mockito如何只模拟超类方法的调用

Java Mockito如何只模拟超类方法的调用,java,mockito,Java,Mockito,我在一些测试中使用Mockito 我有以下课程: class BaseService { public void save() {...} } public Childservice extends BaseService { public void save(){ //some code super.save(); } } 我只想模拟ChildService的第二个调用(super.save)。第一个调用

我在一些测试中使用Mockito

我有以下课程:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

我只想模拟
ChildService
的第二个调用(
super.save
)。第一个调用必须调用实方法。有没有办法做到这一点?

没有,Mockito不支持这一点

这可能不是您想要的答案,但您看到的是不应用设计原则的症状:

如果您提取一个策略而不是扩展一个超类,那么问题就消失了

但是,如果不允许您更改代码,但无论如何您都必须测试它,并且以这种尴尬的方式,仍然有希望。使用一些AOP工具(例如AspectJ),您可以将代码编织到超类方法中,并完全避免其执行(糟糕)。如果您使用的是代理,那么这不起作用,您必须使用字节码修改(加载时编织或编译时编织)。还有一些模拟框架也支持这种技巧,比如PowerMock和PowerMockito


我建议你去进行重构,但如果这不是一个选项,你将获得一些严肃的黑客乐趣。

如果你真的没有重构的选择,你可以在超级方法调用中模拟/存根一切,例如

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

在调用超类方法的子类中创建一个包保护(假定测试类在同一个包中)方法,然后在重写的子类方法中调用该方法。然后,您可以通过使用spy模式在测试中设置对该方法的期望。不太好,但肯定比在测试中处理super方法的所有期望设置要好

考虑将代码从ChildService.save()方法重构为其他方法,并测试新方法,而不是测试ChildService.save(),这样可以避免对super方法的不必要调用

例如:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

原因是您的基类没有被公共化,那么Mockito由于可见性而无法拦截它,如果您将基类更改为public,或者将子类中的@Override更改为public,那么Mockito可以正确地模拟它

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

如果继承有意义的话,可能最简单的选择是创建一个新方法(包private???)来调用super(让我们称它为superFindall),监视真实实例,然后以您想要模拟父类的方式模拟superFindall()方法。就覆盖率和可视性而言,这并不是一个完美的解决方案,但它应该可以胜任这项工作,而且很容易应用

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}

即使我完全同意我的回答(

重组合轻继承

),我承认有些时候继承看起来很自然,我不觉得仅仅为了单元测试而破坏或重构它

因此,我的建议是:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}
然后,在单元测试中:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done
ChildService childSrv=Mockito.mock(ChildService.class,Mockito.CALLS\u REAL\u方法);
Mockito.doAnswer(新答案(){
@凌驾
公共布尔应答(调用锁调用)
扔掉的{
//将您对BaseService.commonSave()的模拟行为放在这里
返回null;
}
}).when(childSrv.commonSave();
childSrv.save();
验证(childSrv,Mockito.times(1)).commonSave();
//放置任何其他断言以检查特定于子级的工作是否完成

我找到了一种使用PowerMockito抑制超类方法的方法。这需要3个简单的步骤

  • 使用PowerMockito.suppress方法和MemberMatcher.methodsDeclaredIn方法来抑制父类方法

  • 第二个在@PrepareForTest中添加父类

  • 使用PowerMock ie在测试类上方添加@RunWith(PowerMockRunner.class)运行测试类

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({BaseService.class})
    public class TestChildService(){
    
        @Spy
        private ChildService testChildServiceObj = Mockito.spy(new ChildService());
    
        @Test
        public void testSave(){
            PowerMockito.suppress(MemberMatcher.methodsDeclaredIn(BaseService.class));
    
            //your further test code
    
            testChildServiceObj.save();
        }
    }
    

  • 注意:这仅在超类方法不返回任何内容时才有效。

    有一种简单的方法适用于大多数情况。您可以监视您的对象并存根您想要模拟的方法

    以下是一个例子:

    MyClass myObjectSpy = Mockito.spy(myObject);
    org.mockito.Mockito.doReturn("yourReturnValue").when(mySpyObject).methodToMock(any()..);
    
    因此,在测试对象时,可以使用myObjectSpy,当调用methodToMock时,它将通过模拟方法覆盖正常行为


    此代码用于返回的方法。如果您有一个void方法,您可以改为使用。

    您可以使用PowerMockito执行此操作,并用继续测试子类方法来替换父类方法的行为。即使当该方法返回某个值(比如字符串)时,也可以执行以下操作:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({ BaseService.class })
    public class TestChildService() {
    
        private BasicService basicServiceObj;
        private ChildService testee;
    
        @Before
        public void init() {
            testee = new ChildService();
            basicServiceObj = PowerMockito.spy(new BaseService());
            PowerMockito.doReturn("Result").when(basicServiceObj, "save", ... optionalArgs);
        }
    
        @Test
        public void testSave(){
            testee.save();
        }
    }
    

    如果不返回任何内容(
    void
    ),则可以使用
    doNothing
    而不是
    doReturn
    。添加一些
    optionalArgs
    如果方法有一些参数,如果没有,则跳过该部分。

    我没有看到LSP冲突。我的设置与OP大致相同:一个带有findAll()方法的基本DAO类,以及一个通过调用super.findAll()然后对结果排序来重写基本方法的子类DAO。子类可以替换到接受超类的所有上下文中。我误解你的意思了吗?我会删除LSP注释(它不会给答案增加价值)。是的,继承很糟糕,我一直使用的愚蠢框架设计的唯一选项是继承。假设你不能重新设计超类,您可以将
    //一些代码
    代码提取到一个可以单独测试的方法中。好的,明白了。这是一个不同于我在寻找它时试图解决的问题,但至少这一误解解决了我自己对基类的模拟调用的问题(我确实没有重写)。这段代码实际上不会阻止super.save()调用,所以如果你在super.save()中做了很多工作您必须阻止所有这些调用…当我想从超类方法返回模拟值供孩子使用时,奇妙的解决方案对我来说非常有效,奇妙的谢谢。除非vali