1

I am trying to use mockito to mock a method. However the class I am injecting mocks with calls the method twice while sending in two different objects of the same type, but depending the values in the object determine the output of the method.

So, for example, If I am trying to mock

public ArrayList<example> attemptToMock(testObject testing)

Lets sat type testObject has a string value in it.

So if the string value in testObject is "OK" then attemptToMock should output an array of two objects in it. If testObject string value is "NO" then the Array list sent out only has one Object.

How to I write a test to handle a call so that a class can call attemptToMock twice, within the same method, and I can mock out its output it so depending on the values within testObject. I can mock it to send out different arrays.

skaffman
  • 381,978
  • 94
  • 789
  • 754
thrilingheroics
  • 13
  • 1
  • 1
  • 3

2 Answers2

3

A few options:

  • Override equals and hashCode on your object (TestObject). This is only feasible if all of the values on your object are predictable, and may be more work than other solutions, but if you need to write equals and hashCode anyway (for Map and Set behavior, for example) this is a reasonable solution.

    // Mockito compares with objects' equals(Object) methods by default.
    when(collaborator.attemptToMock(object1)).thenReturn(array1);
    when(collaborator.attemptToMock(object2)).thenReturn(array2);
    
  • Write a Hamcrest matcher and use that to match the arrays. This acts as a compact analogue to equals for a specific case, and is especially handy if you need to change behavior based on the same value in many tests.

    public class IsATestObjectWithValue extends TypeSafeMatcher<TestObject> {
      private final String expectedValue;
    
      public IsATestObjectWithValue(String expectedValue) {
        super(TestObject.class);
        this.expectedValue = expectedValue;
      }
    
      @Override public void matchesSafely(TestObject object) {
        // object will never be null, but object.value might.
        return expectedValue.equals(object.value);
      }
    }
    

    Now you can write an equivalent match as above:

    when(collaborator.attemptToMock(argThat(new IsATestObjectWithValue("OK")))
        .thenReturn(array1);
    when(collaborator.attemptToMock(argThat(new IsATestObjectWithValue("NO")))
        .thenReturn(array2);
    
  • Use an Answer, as wdf described. Anonymous inner Answers are common and pretty concise, and you get access to all of the arguments. This is especially good for one-off solutions, or if you want to explicitly and immediately fail the test if an invalid value (testObject.value) is passed in.

  • As a last resort, if the order of the calls is predictable, you can return multiple values in sequence.

    when(collaborator.attemptToMock(any(TestObject.class)))
        .thenReturn(array1).thenReturn(array2);
    when(collaborator.attemptToMock(any(TestObject.class)))
        .thenReturn(array1, array2);  // equivalent
    

    Either of the above lines will return array1 for the first call and array2 for the second call and all calls after it, regardless of the parameter. This solution will be much more brittle than your original question asks for--it'll fail if the call order changes, or if either of the calls is edited out or repeated--but is sometimes the most compact solution if the test is very temporary or if the order is absolutely fixed.

Community
  • 1
  • 1
Jeff Bowman
  • 74,544
  • 12
  • 183
  • 213
0

You can access the parameters passed into a mocked method invocation and vary the return value accordingly by using the Answer interface. See the answer to this question, and the docs for Answer.

Basically, the only weird/non-obvious thing going on here is that you have to downcast the parameters to the type you are expecting. So, in your case, if you are mocking a method that takes a single 'TestObject' parameter, then you'll have to do something like this inside of your 'answer' implementation:

Object[] args = invocation.getArguments();
TestObject testObj = (TestObject) args[0];
if ("OK".equals(testObj.value)) {
  return new ArrayList(value1, value2);
} else if ("NO".equals(testObj.value)) {
  return new ArrayList(singleObject);
}
Community
  • 1
  • 1
wdf
  • 171
  • 5