6

Using Mockito, is there a way to spy() on an object and verify that an object is called a given # of times with the specified arugments AND that it returns an expected value for these calls?

I'd like to do something like the following:

class HatesTwos {
  boolean hates(int val) {
    return val == 2;
  }
}

HatesTwos hater = spy(new HatesTwos());
hater.hates(1);
assertFalse(verify(hater, times(1)).hates(1));

reset(hater);
hater.hates(2);
assertTrue(verify(hater, times(1)).hates(2));
Chris Morris
  • 4,335
  • 4
  • 24
  • 28
  • 2
    It sounds like you're trying to test two classes at once. You're testing the class that you're spying on, because you want to test that it returns the expected value. You're testing the class that CALLS the one that you're spying on, because you want to test what arguments are passed. That seems like two separate tests to me, and I would strongly advise AGAINST trying to roll them into one. – Dawood ibn Kareem Sep 20 '13 at 05:57
  • But what if I have a class that calls its own methods (which is reasonable behavior)? Then testing that MyClass.funcThatGetsCalledByAnotherMyClassFunc() returns a certain value and that it is called a certain amount of times are both valid. – Chris Morris Sep 20 '13 at 23:21
  • Are they really both valid? Focus on testing that the behaviour of your class is correct, not that its implementation is what you believe it to be. When you test method A, you should really care just about the output of method A, not whether it calls method B and what arguments it passes. If you want to test method B as well, of course you can. – Dawood ibn Kareem Sep 20 '13 at 23:41
  • @DavidWallace I don't see why wanting to verify both the output and behavior of a method doesn't make sense. Either way, do you have any insight into my original post? – Chris Morris Sep 23 '13 at 16:17
  • @ChrisMorris 3 years have passed: you're probably a testing guru by now. Do you have any insight into the above comments now? As a testing newb I find the idea that "we only test the functional interface" unsatisfactory: I think we might be inclined to use such a thing as a "testing interface", which could be a superset of the "functional interface", and so might call its own methods... – mike rodent Nov 16 '16 at 18:09
  • @mikerodent In general, I agree with David. In most cases, you should care about the behavior of the interface and not the implementation. I don't remember the details of what I was trying to do when I posted this question. There exceptions to almost every rule, though, so use your judgement. – Chris Morris Nov 23 '16 at 23:47

1 Answers1

10

You could use the Answer interface to capture a real response.

public class ResultCaptor<T> implements Answer {
    private T result = null;
    public T getResult() {
        return result;
    }

    @Override
    public T answer(InvocationOnMock invocationOnMock) throws Throwable {
        result = (T) invocationOnMock.callRealMethod();
        return result;
    }
}

Intended usage:

class HatesTwos {
    boolean hates(int val) {
        return val == 2;
    }
}

HatesTwos hater = spy(new HatesTwos());

// let's capture the return values from hater.hates(int)
ResultCaptor<Boolean> hateResultCaptor = new ResultCaptor<>();
doAnswer(hateResultCaptor).when(hater).hates(anyInt());

hater.hates(1);
verify(hater, times(1)).hates(1);
assertFalse(hateResultCaptor.getResult());

reset(hater);

hater.hates(2);
verify(hater, times(1)).hates(2);
assertTrue(hateResultCaptor.getResult());
Jeff Fairley
  • 8,071
  • 7
  • 46
  • 55
  • This doesn't work for me... in some code of mine the `doAnswer` method call did not result in a call to `ResultCaptor.answer`. When I then copied your code verbatim to an experiment class, running the second bit in `main`, the 1st test (`assertFalse`) passed (false), after calling `answer`. The 2nd test (`assertTrue`) failed, returning `false` and not actually calling `answer`! – mike rodent Nov 17 '16 at 15:48
  • Ah... but it passed when I made a `HatesTwo hater2` and got rid of `reset`. In the (current) Mockito Javadoc it recommends not to use `Mockito.reset()`.. – mike rodent Nov 17 '16 at 15:55