简体   繁体   English

模拟一个抽象类的其余部分,但在其中调用真实方法?

[英]Mock the rest of an abstract class but call the real methods in it?

There is some interface called say called Foo . 有一些名为say的接口称为Foo

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

There is an implementation in the test called MockFoo that implements most methods in a "default" way (doing nothing, returning null , basically only implementing them so it compiles). 测试中有一个称为MockFoo的实现,该实现以“默认”方式实现大多数方法(不执行任何操作,返回null ,基本上只实现它们以便编译)。 However, it implements a couple to give real functionality, and they are methods that insert and read from a Map . 但是,它实现了一对夫妇,以提供真正的功能,并且它们是可从Map插入和读取的方法。 (If the last bit wasn't essential I would just use a Mockito mock and wouldn't even be asking.) (如果最后一点不是必不可少的,那么我只会使用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()
    {}   
}

The problem is that because this is not a Mockito mock, every time the interface changes the test has to be updated. 问题在于,由于这不是Mockito模拟,因此每次界面更改时都必须更新测试。 Yes, people should check better to fix all implementations of the interface they change, but there really shouldn't be an implementation in the test in the first place in my opinion. 是的,人们应该进行更好的检查以修复他们更改的接口的所有实现,但是我认为实际上不应首先在测试中实现任何实现。

I am confused on how to solve this. 我对如何解决这个问题感到困惑。 My instinct is to make it abstract and implement only those methods then mock it somehow so that it calls those "real" methods when it needs them. 我的本能是使它抽象并仅实现那些方法,然后以某种方式对其进行模拟,以便在需要时调用那些“真实”方法。 I read that Mockito has a thenDoRealMethod() for stubs but this is only for returning values so it would not work on a void method. 我读到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);
    } 
}

I realize this may be a design issue and adding in an AbstractFoo in the real code is probably best (because the add and get won't change really) but I am more curious if there is a way to fix this once-and-for-all just by modifying the test code. 我意识到这可能是一个设计问题,在真实代码中添加AbstractFoo可能是最好的方法(因为添加和获取不会真正改变),但我更想知道是否有一种解决方法-只需修改测试代码即可。

Using a technique like in this SO answer , you can use CALLS_REAL_METHODS as a default answer—or, as you suggested, you can use the default answer (RETURNS_DEFAULTS) and individually stub certain methods to call your fake. 使用这种SO答案中的技术,您可以将CALLS_REAL_METHODS用作默认答案,或者,如您建议的那样,您可以使用默认答案(RETURNS_DEFAULTS)并单独对某些方法进行存根调用来调用您的假冒产品。 Because you want Mockito's behavior to show through, I'd recommend the latter. 因为您希望Mockito的行为表现出来,所以我建议后者。

There's an equivalent to thenCallRealMethod for void methods: doCallRealMethod . 有一个等效于thenCallRealMethodvoid方法: doCallRealMethod You need to start with do because when(T value) has no value to take [and subsequently ignore]. 您需要从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);
    }
}

Without complicated reflection, I can't think of any way to automatically let Mockito call real methods for unimplemented methods only. 没有复杂的思考,我想不出任何办法让Mockito仅针对未实现的方法自动调用真实方法。 If you were to write an Answer for that, though, you could use it for all methods by passing it into the call to mock . 但是,如果您要为此编写一个Answer,则可以将其传递给mock调用来将其用于所有方法。 You will also need to initialize your field explicitly in your create method, as the mock is not a real object and will not be properly initialized. 您还需要在create方法中显式初始化字段,因为模拟不是真实的对象,也不会正确初始化。

Another alternative would be to have empty method stubs, and use spy() instead; 另一种选择是使用空的方法存根,并使用spy()代替。 this would solve some of the initialization concerns. 这将解决一些初始化问题。


This technique is called partial mocking , and beware that it can act as a code smell —specifically, that the class under test is violating the Single Responsibility Principle. 这种技术称为部分嘲讽 ,要当心它可以充当代码的味道 ,特别是要测试的类违反了“单一职责原则”。 In any case you should be very careful when adding methods to widely-implemented interfaces, for all of these reasons, and consider AbstractFoo (or a fully-implemented FakeFoo ) as sensible upgrades later. 无论如何,出于所有这些原因,在将方法添加到广泛实现的接口中时应格外小心,并在以后将AbstractFoo (或完全实现的FakeFoo )视为明智的升级。

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

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