简体   繁体   English

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

[英]Mockito How to mock only the call of a method of the superclass

I'm using Mockito in some tests.我在一些测试中使用 Mockito。

I have the following classes:我有以下课程:

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

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

I want to mock only the second call ( super.save ) of ChildService .我想嘲笑只有第二个呼叫( super.save的) ChildService The first call must call the real method.第一次调用必须调用真正的方法。 Is there a way to do that?有没有办法做到这一点?

If you really don't have a choice for refactoring you can mock/stub everything in the super method call eg如果你真的没有选择重构,你可以模拟/存根超级方法调用中的所有内容,例如

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

No, Mockito does not support this.不,Mockito 不支持这一点。

This might not be the answer you're looking for, but what you're seeing is a symptom of not applying the design principle:这可能不是您正在寻找的答案,但您所看到的是不应用设计原则的症状:

Favor composition over inheritance 喜欢组合而不是继承

If you extract a strategy instead of extending a super class the problem is gone.如果您提取策略而不是扩展超类,问题就消失了。

If however you are not allowed to change the code, but you must test it anyway, and in this awkward way, there is still hope.但是,如果您不允许更改代码,但无论如何您必须对其进行测试,并且以这种尴尬的方式,仍然有希望。 With some AOP tools (for example AspectJ) you can weave code into the super class method and avoid its execution entirely (yuck).使用一些 AOP 工具(例如 AspectJ),您可以将代码编织到超类方法中并完全避免其执行(糟糕)。 This doesn't work if you're using proxies, you have to use bytecode modification (either load time weaving or compile time weaving).如果您使用代理,这不起作用,您必须使用字节码修改(加载时编织或编译时编织)。 There are be mocking frameworks that support this type of trick as well, like PowerMock and PowerMockito.还有一些模拟框架也支持这种技巧,比如 PowerMock 和 PowerMockito。

I suggest you go for the refactoring, but if that is not an option you're in for some serious hacking fun.我建议您进行重构,但如果这不是一个选项,那么您将获得一些严肃的黑客乐趣。

Consider refactoring the code from ChildService.save() method to different method and test that new method instead of testing ChildService.save(), this way you will avoid unnecessary call to super method.考虑将 ChildService.save() 方法的代码重构为不同的方法并测试该新方法而不是测试 ChildService.save(),这样您将避免对超级方法的不必要调用。

Example:例子:

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

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

I found a way to suppress the superclass method using PowerMockito.我找到了一种使用 PowerMockito 抑制超类方法的方法。 3 simple steps need for this这需要3个简单的步骤

  1. Use PowerMockito.suppress method and MemberMatcher.methodsDeclaredIn method to supress parent class method使用PowerMockito.suppress方法和MemberMatcher.methodsDeclaredIn方法来抑制父类方法

  2. Second add Parent class in @PrepareForTest第二个在@PrepareForTest 中添加父类

  3. Run your test class with PowerMock ie add @RunWith(PowerMockRunner.class) above your test class.使用 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(); } }

Note: This will work only when the superclass method does not return anything.注意:这仅在超类方法不返回任何内容时有效。

Maybe the easiest option if inheritance makes sense is to create a new method (package private??) to call the super (lets call it superFindall), spy the real instance and then mock the superFindAll() method in the way you wanted to mock the parent class one.如果继承有意义,也许最简单的选择是创建一个新方法(包私有??)来调用超级(让我们称之为 superFindall),监视真实实例,然后以您想要模拟的方式模拟 superFindAll() 方法父类一。 It's not the perfect solution in terms of coverage and visibility but it should do the job and it's easy to apply.就覆盖范围和可见性而言,这不是完美的解决方案,但它应该可以完成工作并且易于应用。

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

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

create a package protected (assumes test class in same package) method in the sub class that calls the super class method and then call that method in your overridden sub class method.在调用超类方法的子类中创建一个受保护的包(假设测试类在同一包中)方法,然后在覆盖的子类方法中调用该方法。 you can then set expectations on this method in your test through the use of the spy pattern.然后,您可以通过使用间谍模式在测试中设置对此方法的期望。 not pretty but certainly better than having to deal with all the expectation setting for the super method in your test不漂亮但肯定比在测试中处理超级方法的所有期望设置要好

Even if i totally agree with iwein response (即使我完全同意 iwein 的回应(

favor composition over inheritance偏向于组合而不是继承

), i admit there are some times inheritance seems just natural, and i don't feel breaking or refactor it just for the sake of a unit test. ),我承认有时候继承看起来很自然,我不觉得为了单元测试而破坏或重构它。

So, my suggestion :所以,我的建议:

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

And then, in the unit test :然后,在单元测试中:

    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

You can do this with PowerMockito and replace behavior only of the parent class method with continuing testing the child's class method.您可以使用 PowerMockito 执行此操作,并通过继续测试子类方法仅替换父类方法的行为。 Even when the method is returning some value, lets say a string, you can do something like this:即使该方法返回某个值,比如一个字符串,您也可以执行以下操作:

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

If you are returning nothing ( void ) then instead of doReturn you can use doNothing .如果你什么都不是(返回void ),然后代替doReturn可以使用doNothing Add some optionalArgs if the method have some arguments, if not, then skip that part.如果方法有一些参数,则添加一些optionalArgs ,如果没有,则跳过该部分。

The reason is your base class is not public-ed, then Mockito cannot intercept it due to visibility, if you change base class as public, or @Override in sub class (as public), then Mockito can mock it correctly.原因是你的基类不是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());
}

There is simple approach that works for most of cases.有一种简单的方法适用于大多数情况。 You can spy your object and stub the method you want to mock.您可以监视对象并存根要模拟的方法。

Here is an example:下面是一个例子:

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

So, when you test your object, you can use myObjectSpy and when methodToMock is called, it will overwrite the normal behavior by a mock method.因此,当您测试您的对象时,您可以使用 myObjectSpy 并且在调用 methodToMock 时,它将通过模拟方法覆盖正常行为。

This code for a method with return.此代码用于带返回的方法。 In case you have a void method you can use doNothing instead.如果你有一个 void 方法,你可以使用doNothing代替。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM