I have an Api class that can make an api call. It just takes a Long and an Integer as Arguments. I call this api in a controller class, which handles the potential errors when calling the api. Essentially it's just this:
public String save() {
try {
String rc = api.doApiCall(l, i);
if(rc.equals("200"))
return "nextpage.html";
} catch (RuntimeException ignored) {
}
return "error.html";
}
I want to test this controller. I'm mocking the api like this:
@BeforeEach
void setUp() {
controller = new Controller();
api = mock(Api.class);
controller.api = api;
}
@Test
void testApiCall() {
when(api.doApiCall(any(Long.class), any(Integer.class)))
.thenReturn("200");
assertEquals("nextpage.html", controller.save());
when(api.doApiCall(any(Long.class), any(Integer.class)))
.thenThrow(new RuntimeException("400"));
assertEquals("error.html", controller.save());
when(api.doApiCall(any(Long.class), any(Integer.class)))
.thenThrow(new RuntimeException("401"));
assertEquals("error.html", controller.save());
}
However executing this test fails at the third when() call because the exception specified in the second when() call is thrown. How can i fix this and why does it happen?
I already experimented a bit and found out that, if you replace the any(Integer.class)
in the third when() call with just any()
the test works as expected but now adding a third test case like
when(api.doApiCall(any(Long.class), any()))
.thenThrow(new RuntimeException("402"));
assertEquals("error.html", controller.save());
fails with the exact same problem as above.
Note that it is always preferable to have distinct test cases for distinct tests - and testing the different reactions to different API results are different test cases.
The problem is that when you write
when(api.doApiCall(any(Long.class), any(Integer.class)))
you are actually calling api.doApiCall()
on the api
mock and this exercises the previously registered behaviour.
api.doApiCall(any(Long.class), any(Integer.class))
returns null and later mockito registers a return value of "200" for succeeding calls.api.doApiCall(any(Long.class), any(Integer.class))
returns "200" and later mockito registers to throw an exception for succeeding calls.api.doApiCall(any(Long.class), any(Integer.class))
throws new RuntimeException("400")
You can avoid this problem if you write
doThrow(new RuntimeException("401"))
.when(api).doApiCall(any(Long.class), any(Integer.class));
But this has still the potential to create unwanted coupling between your distinct tests.