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

时间:2010-08-12 12:50:21

标签: mockito

我在一些测试中使用Mockito。

我有以下课程:

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

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

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

10 个答案:

答案 0 :(得分:76)

如果你真的没有选择进行重构,你可以在超级方法调用中模拟/存储所有内容,例如。

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

答案 1 :(得分:53)

不,Mockito不支持这一点。

这可能不是您正在寻找的答案,但您所看到的是不应用设计原则的症状:

  

Favor composition over inheritance

如果您提取策略而不是扩展超类,问题就会消失。

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

我建议你去重构,但如果这不是一个选项,那么你就会有一些严肃的黑客攻击。

答案 2 :(得分:4)

考虑将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
    }
} 

答案 3 :(得分:1)

在调用超类方法的子类中创建一个受保护的包(假定同一包中的测试类)方法,然后在重写的子类方法中调用该方法。然后,您可以通过使用间谍模式在测试中设置对此方法的期望。不是很漂亮,但肯定比在测试中处理超级方法的所有期望设置更好

答案 4 :(得分:1)

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

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

  2. 第二个在@PrepareForTest中添加父类

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

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

答案 5 :(得分:0)

原因是你的基类没有被公开,那么Mockito由于可见性不能拦截它,如果你将基类改为public,或者在子类中改为@Override(作为公共),那么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());
}

答案 6 :(得分:0)

如果继承有意义,也许最简单的选择是创建一个新方法(包私有?)来调用super(让它称为superFindall),监视真实实例然后以你的方式模拟superFindAll()方法想要模拟父类一。它在覆盖范围和可见性方面不是完美的解决方案,但它应该能够胜任并且易于应用。

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

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

答案 7 :(得分:0)

即使我完全同意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

答案 8 :(得分:0)

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

这是一个例子:

MyClass myObjectSpy = Mockito.spy(myObject);
org.mockito.Mockito.doReturn("yourReturnValue").when(mySpyObject).methodToMock(any()..);

因此,当您测试对象时,可以使用 myObjectSpy,并且在调用 methodToMock 时,它将通过模拟方法覆盖正常行为。

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

答案 9 :(得分:0)

您可以使用 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),那么您可以使用 doReturn 代替 doNothing。如果方法有一些参数,则添加一些 optionalArgs,如果没有,则跳过该部分。