16

I ran into a problem with mockito. I am developing a web application. In my tests the user management is mocked. There are some cases when I have to alter the User returned by the getLoggedInUser() method.

The problem is, that my getLoggedInUser() method can also throw an AuthenticationException.

So when I try to switch from no user to some user, the call to

when(userProvider.getLoggedInUser()).thenReturn(user);

throws an exception, as userProvider.getLoggedInUser() is already stubbed with thenTrow()

Is there any way for to tell the when method not to care about exceptions?

Thanks in advance - István

Sobvan
  • 1,376
  • 1
  • 15
  • 24
  • Thank you guys for the answers! To sum up: it is likely because of poor design of the software that I need to restub the method. But for now it is easy for me, and the tests also look clean. I did some more research and have found the Mockito.reset(T... mocks) method, that does the trick for me. Next time I will figure out some more simple design:) – Sobvan Nov 16 '10 at 10:20

4 Answers4

24

In new Mockito versions you can use stubbing consecutive calls to throw exception on first can and returning a value on a second call.

when(mock.someMethod("some arg"))
    .thenThrow(new RuntimeException())
    .thenReturn("foo");

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#10

jervi
  • 160
  • 1
  • 8
Bogdan Sulima
  • 418
  • 4
  • 10
5

My first reaction to your question is that it sounds like you are trying to do too much in one test.

For ease of testing and simplicity each test should test one thing only. This is the same as the Single Responsibility Principle. I often find programmers trying to test multiple things in one test and having all sorts of problems because of it. So each of your unit test methods should follow this flow:

  1. Setup a single scenario for the test.
  2. Make a call to the class being tested to trigger the code being tested.
  3. Verify the behaviour.

So in your case I would expect to see at least two tests. One where getLoggedInUser() returns a user, and one where getLoggedInUser() throws an exception. That way you will not have problems with trying to simulate different behaviour in the mock.

The second thought that spring to mind is not to stub. Look into using expect instead because you can setup a series of expectation. I.e. the first call returns a user, the second call throws an exception, the third call returns a different user, etc.

drekka
  • 20,957
  • 14
  • 79
  • 135
5

Is there any way for to tell the when method not to care about exceptions?

To actually answer this question:

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;

import org.junit.Test;
import org.mockito.Mockito;

import java.util.ArrayList;

public class MyTest {

    @Test
    public void testA() {

        // setup
        ArrayList<Object> list = mock(ObjectArrayList.class);
        when(list.indexOf(any())).thenReturn(6);
        when(list.indexOf(any())).thenReturn(12);

        // execute
        int index = list.indexOf(new Object());

        // verify
        assertThat(index, is(equalTo(12)));
    }

    @Test
    public void testB() {

        // setup
        ArrayList<Object> list = mock(ObjectArrayList.class);
        when(list.add(any())).thenThrow(new AssertionError("can't get rid of me!"));
        when(list.add(any())).thenReturn(true);

        // execute
        list.add(new Object());
    }

    @Test
    public void testC() {

        // setup
        ArrayList<Object> list = mock(ObjectArrayList.class);
        when(list.add(any())).thenThrow(new AssertionError("can't get rid of me!"));
        Mockito.reset(list);
        when(list.add(any())).thenReturn(true);

        // execute
        list.add(new Object());
    }

    /**
     * Exists to work around the fact that mocking an ArrayList<Object>
     * requires a cast, which causes "unchecked" warnings, that can only be suppressed...
     */
    class ObjectArrayList extends ArrayList<Object> {

    }
}

TestB fails due to the assert that you can't get rid of. TestC shows how the reset method can be used to reset the mock and remove the thenThrow command on it.

Note that reset doesn't always seem to work in some more complicated examples I have. I suspect it might be because they're using PowerMockito.mock rather than Mockito.mock?

Pod
  • 3,938
  • 2
  • 37
  • 45
0

Use Mockito.reset() to reset any particular mocks, for example Mockito.reset(mock1, mock2)

Check for more detail: https://stackoverflow.com/a/68126634/12085680

For example:

    @Test
    void test() {   
    when(mock.someMethod(any())).thenThrow(SomeException.class);
    // Using a service that uses the mock inside for example
    assertThrows(SomeException.class, () -> service.someMethodThatUsesTheMock("test"));
    Mockito.reset(mock); // reset it so you can reuse that mock on another test
    }
BugsOverflow
  • 386
  • 3
  • 19