0

There is some interface called say called 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). However, it implements a couple to give real functionality, and they are methods that insert and read from a Map. (If the last bit wasn't essential I would just use a Mockito mock and wouldn't even be asking.)

// 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. 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.

// 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.

Captain Man
  • 6,997
  • 6
  • 48
  • 74

1 Answers1

3

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. Because you want Mockito's behavior to show through, I'd recommend the latter.

There's an equivalent to thenCallRealMethod for void methods: doCallRealMethod. You need to start with do because when(T value) has no value to take [and subsequently ignore].

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

Another alternative would be to have empty method stubs, and use spy() instead; 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.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Can you update the answer? I did need to do `mockFoo.map = new ...` in the `create()` method for the reason you said. Also, I am confused, what benefit is a `FakeFoo`? That's basically what I had to start and what I was trying to avoid. – Captain Man Aug 04 '15 at 20:37
  • 1
    Answer updated. My point in mentioning `FakeFoo` is to agree that a robust and predictable implementation (rather than a halfway-functional partial mock) is the "best way" for readable and maintainable code, and that if the codebase you're testing is long-lived you may treat a fully-spec'ed test double as a good investment of time. If your code is temporary, or if your interface changes frequently outside of your control, then a partial mock may be the right choice. (Of course, you could also refactor the value-object Map behavior out of your Foo, which might be even better.) – Jeff Bowman Aug 04 '15 at 22:31