Oleg Eterevsky
Oleg Eterevsky

Reputation: 1614

How to make Mockito throw an exception when a mock is called with non-defined parameters?

Is it possible to throw an exception whenever a mock is called with non-predefined arguments? There is Answers.RETURNS_SMART_NULLS, but it's not really what I need, since it doesn't work if null is legitimate return value, which doesn't lead to NullPointerException, but rather to errors later on.

Edit: some background. So, in Mockito when you define a mock, you specify the return values for each call like this:

when(myMock.someMethod(arg1, arg2)).thenReturn(returnValue);

When myMock.someMethod is called with arguments, for which I didn't give a return value in the test, it just returns null. I would like to configure it to crash right away and tell me that I forgot to define the return value for some combination of parameters.

Edit 2: There were suggestions to provide a custom defaultAnswer that would throw exceptions when called. Unfortunately, this doesn't work. The default answers' answer() method is called even if a mock is present. Here's a sample:

public class Test {
  public static class Adder {
    public int add(int a, int b) {
      return a + b;
    }
  }

  public static final Answer<Object> THROW_ON_UNDEFINED_ARGS = new Answer<Object>() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
      throw new IllegalArgumentException(
          String.format("Calling a mock with undefined arguments: %s %s",
              invocation.getMethod(),
              Arrays.toString(invocation.getArguments())));
    }
  };

  public static void main(String[] args) {
    Adder adderMock = mock(Adder.class, THROW_ON_UNDEFINED_ARGS);
    when(adderMock.add(2, 3)).thenReturn(5);
    System.out.println(adderMock.add(2, 3));
  }
}

The exception is thrown even though adderMock.add(2, 3) is defined.

Upvotes: 33

Views: 33340

Answers (3)

Guillaume Polet
Guillaume Polet

Reputation: 47608

You could provide a default Answer in the construction of your mock that always throws an exception. Then every call that is stubbed will act like usual. Everything outside those paths will throw an exception. Something like this:

final String arg = "some arg";
Collection<Object> object = mock(Collection.class, new Answer<Object>() {
    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        throw new IllegalArgumentException("You cannot invoke " + invocation.getMethod() +
                                    " with " + Arrays.toString(invocation.getArguments()));
    }
});
doReturn(true).when(object).add(arg);

object.add(arg); // Goes ok
object.add("azertyuiop"); // Throws the exception

Upvotes: 23

troig
troig

Reputation: 7212

Just point another way you can do that, using thenAnswer:

when(myMock.someMethod(anyString(), anyString())).
            thenAnswer(new Answer<String>() {
               @Override
               public String answer(InvocationOnMock invocation) throws Throwable {
                  Object[] args = invocation.getArguments();
                  String arg1 = (String) args[0];
                  String arg2 = (String) args[1];

                  if ("arg1".equals(arg1) && "arg2".equals(arg2)) return "someValue";

                  throw new Exception();
               }
            });

      myMock.someMethod("arg1", "arg2"); // Returns "someValue"
      myMock.someMethod("xxx", "yyy");   // Throws Exception

Hope it helps.

Upvotes: 7

M4ks
M4ks

Reputation: 12024

First, a bit of "good engineering" mumble - why would you like to do this? Mockito tries to 'promote' BDD style - you set up (mock) your calls, you execute the code and verify interactions were exactly as you expected rather then 'it didn't called anything else' - Do you try to do something described in Finding irrelevant invocation? Generally if I want to mock all the cases, but one - this makes my ask myself whether my tests are really OK.

Anyway, to the topic :)

In Mockito, you can define multiple whens with different values, like

class Foo {
   public String bar(int a) {
       return "bar = " + a;
   }
}

Mockito.when(task.bar(Matchers.anyInt())).thenReturn("L")
Mockito.when(task.bar(3)).thenThrow(new IllegalAccessError())

task.bar(4); // returns "L" 
task.bar(3); //throws IllegalAccessError

Notice that the order of whens DOES matter. The rules are processed in reversed order (or rather overrides the actual matchers). In my code we first mock for anyInt, then for 3 - which works. If you reverse it - both calls to bar() will return 'L'.

Upvotes: 4

Related Questions