Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.

like image 732
Oleg Eterevsky Avatar asked Apr 09 '15 10:04

Oleg Eterevsky


2 Answers

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 
like image 122
Guillaume Polet Avatar answered Sep 17 '22 22:09

Guillaume Polet


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

like image 30
M4ks Avatar answered Sep 20 '22 22:09

M4ks