如何只模仿超类的方法的调用

我在一些测试中使用了 Mockito。

我有以下课程:

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


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

我只想模仿 ChildService的第二个调用(super.save)。第一个调用必须调用 real 方法。有办法吗?

152358 次浏览

如果您真的没有选择重构,那么您可以在 super 方法调用中模拟/存根所有内容,例如。

    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();
}

在调用超类方法的子类中创建一个包 protected (假设测试类在同一个包中)方法,然后在重写的子类方法中调用该方法。然后,您可以通过间谍模式的使用在测试中对此方法设置期望值。虽然不是很漂亮,但肯定比必须处理测试中 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
}
}

原因是你的基类不是 public-ed,那么 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) ,侦察实例,然后按照您希望模拟父类 one 的方式模拟 superFindAll ()方法。就覆盖面和可见性而言,这不是一个完美的解决方案,但它应该能起作用,而且很容易应用。

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


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

即使我完全同意 iwein 的回应(

喜欢合成胜过继承

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

所以,我的建议是:

/**
* 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

我找到了一种使用 PowerMockito 来抑制超类方法的方法

  1. 使用 PowerMockito.suppress方法和 MemberMatcher.methodsDeclaredIn方法取消父类方法

  2. Second < strong > 在@PrepareForTest 中添加 Parent 类

  3. 使用 PowerMock 运行测试类,即在测试类的上方添加 @ 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 时,它将通过 mock 方法覆盖正常行为。

这是一个带有 return 的方法的代码。如果你有一个 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,如果没有,则跳过该部分。