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


94

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

我有以下课程:

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

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

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


可以用PowerMockito解决吗?
javaPlease42 '16

@ javaPlease42:是的,您可以:stackoverflow.com/a/23884011/2049986
雅各布·范·林根

Answers:


57

不,Mockito不支持此功能。

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

偏爱继承而不是继承

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

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

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


5
我没有看到LSP违规。我与OP具有大致相同的设置:具有findAll()方法的基本DAO类,以及通过调用super.findAll()然后对结果进行排序来覆盖基本方法的DAO子类。子类可替换为接受超类的所有上下文。我误会你的意思吗?

1
我将删除LSP注释(它不会为答案增加价值)。
iwein

是的,继承很烂,而我所坚持的愚蠢框架是将继承作为唯一选项而设计的。
Sridhar Sarnobat,2015年

假设您无法重新设计超类,则可以将//some codes代码提取到可以单独测试的方法中。
Phasmal '16

1
好的,我知道了。这与我在寻找时试图解决的问题不同,但是至少这种误解解决了我自己的问题,该问题导致我无法从基类进行模拟调用(实际上我并未覆盖)。
Guillaume Perrot

86

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

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

2
该代码实际上并不会阻止super.save()调用权,因此,如果您在super.save()中做很多事情,就必须阻止所有这些调用……
iwein 2011年

当我想从超类方法返回模拟值供孩子使用时,奇妙的解决方案为我创造了奇迹,奇妙的谢谢。
古纳德(Gornard)

14
除非隐藏validate或save方法直接执行工作,而不是调用其他方法,否则这样做效果很好。Mockito不会:Mockito.doNothing()。when((BaseService)spy).save(); 这会不会“doNothing的基本服务,但保存在childService保存:(
蒂维

我无法在我的系统上使用它-它也取消了child方法。BaseService是抽象的,尽管我不明白为什么这样才有意义。
Sridhar Sarnobat,2015年

1
@ Sridhar-Sarnobat是的,我看到的是同一件事:((任何人都知道如何仅将其存根super.validate()吗?
stantonk 2016年

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
    }
} 

1

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


我还可以说组成比继承几乎总是更好,但有时使用继承就更简单了。直到java合并了更好的构图模型(例如scala或groovy),情况始终如此,并且这个问题将继续存在
路加福音

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

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

8
这不是重点。ChildService应该覆盖foo(),问题是如何模拟BaseService.foo()而不是ChildService.foo()
Adriaan Koster

0

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

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

    void superSave(){
        super.save();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.