嘲笑抽象类的其余部分但是调用真正的方法呢?

时间:2015-08-04 15:00:30

标签: java unit-testing mocking mockito

有一个名为Foo的界面。

interface Foo {
    void add(Object key, Object value);
    Object get(Object key);
    void someOtherMethodUnessentialToTesting();
}

在测试中有一个名为MockFoo的实现以“默认”方式实现大多数方法(什么都不做,返回null,基本上只实现它们以便编译)。但是,它实现了一对来提供真正的功能,它们是从Map插入和读取的方法。 (如果最后一点不重要,我会使用Mockito模拟,甚至不会问。)

// The current "bad mock"
class MockFoo implements Foo {

    Map<Object, Object> map = new ...

    @Override
    void add(Object key, Object value) {
        map.put(key, value);
    }

    @Override
    Object get(Object key) {
        map.get(key);
    }

    @Override
    void someOtherMethodUnessentialToTesting()
    {}   
}

问题在于,因为这不是Mockito模拟,每次界面更改时都必须更新测试。是的,人们应该更好地检查以修复他们更改的界面的所有实现,但在我看来,确实不应该是测试中的实现。

我很困惑如何解决这个问题。我的直觉是使它抽象并只实现那些方法,然后以某种方式模拟它,以便它在需要时调用那些“真正的”方法。我读到Mockito有一个thenDoRealMethod()存根,但这只是为了返回值,所以它不适用于void方法。

// My abstract class I was trying to stub somehow
abstract class FooForTest implements Foo {

    Map<Object, Object> map = new ...

    @Override
    void add(Object key, Object value) {
        map.put(key, value);
    }

    @Override
    Object get(Object key) {
        map.get(key);
    } 
}

我意识到这可能是一个设计问题,在真实代码中添加AbstractFoo可能是最好的(因为添加和获取不会真正改变)但我更好奇是否有办法只需修改测试代码就可以解决这个问题。

1 个答案:

答案 0 :(得分:1)

使用像this SO answer这样的技术,你可以使用CALLS_REAL_METHODS作为默认答案 - 或者,如你的建议,你可以使用默认答案(RETURNS_DEFAULTS)并单独存根某些方法来调用你的假。因为你想要通过Mockito的行为,我会推荐后者。

thenCallRealMethod方法的void相当于:doCallRealMethod。您需要从do开始,因为when(T value)没有值[并随后忽略]。

abstract class FooForTest implements Foo {

    public static Foo create() {
        FooForTest mockFoo = mock(FooForTest.class);
        mockFoo.map = new HashMap<>();
        when(mockFoo.get(any())).thenCallRealMethod();
        doCallRealMethod().when(mockFoo).add(any(), any());
        return mockFoo;
    }        

    Map<Object, Object> map = new ...

    @Override
    void add(Object key, Object value) {
        map.put(key, value);
    }

    @Override
    Object get(Object key) {
        map.get(key);
    }
}

没有复杂的反思,我想不出任何方法可以自动让Mockito为未实现的方法调用真正的方法。但是,如果您要为此编写答案,则可以将其用于所有方法,方法是将其传递给mock。您还需要在create方法中显式初始化字段,因为模拟不是真实对象,并且无法正确初始化。

另一种选择是使用空方法存根,而使用spy()代替;这将解决一些初始化问题。

这种技术被称为部分模拟,并且要注意它可以充当code smell - 具体来说,被测试的类违反了单一责任原则。在任何情况下,出于所有这些原因,在向广泛实现的接口添加方法时应该非常小心,并且考虑AbstractFoo(或完全实现的FakeFoo)作为后来的合理升级。