113

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

我有以下课程:

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

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

我只想模拟super.save. ChildService第一次调用必须调用真正的方法。有没有办法做到这一点?

4

10 回答 10

99

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

    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();
    }
于 2010-10-01T11:18:59.090 回答
65

不,Mockito 不支持这一点。

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

优先组合而不是继承

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

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

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

于 2010-08-13T17:59:29.320 回答
6

考虑将代码从 ChildService.save() 方法重构为不同的方法并测试该新方法而不是测试 ChildService.save(),这样您将避免对超级方法的不必要调用。

例子:

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

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 
于 2013-05-17T21:27:11.663 回答
3

我找到了一种使用 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();
        }
    }
    

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

于 2021-03-11T14:27:28.033 回答
2

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

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

    void superSave(){
        super.save();
    }
}
于 2017-05-10T19:59:44.337 回答
1

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

于 2012-05-22T22:04:12.910 回答
1

即使我完全同意 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
于 2018-04-04T16:27:30.033 回答
1

您可以使用 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,如果没有,则跳过该部分。

于 2021-05-13T09:59:01.363 回答
0

原因是您的基类不是公开的,那么 Mockito 由于可见性而无法拦截它,如果您将基类更改为公开,或者将子类中的 @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());
}
于 2013-07-10T09:48:25.413 回答
-1

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

这是一个例子:

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

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

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

于 2021-05-10T23:14:42.417 回答