Micronaut HttpClient testing with Mockito (Java)

64 Views Asked by At

I am trying to test the behaviour of a method in a service class. For that, I need to mock what httpClient.toBlocking().exchange( will return when called. I am trying to do this, using when - thenReturn, but I get the Error: java.lang.NullPointerException: Cannot invoke "io.micronaut.http.client.BlockingHttpClient.exchange(io.micronaut.http.HttpRequest, java.lang.Class)" because the return value of "io.micronaut.http.client.HttpClient.toBlocking()" is null

@MicronautTest public class WeatherServiceTest {

static final String HOUSE_NUM = "testNum";
static final String STREET = "testStreet";
static final String POST_CODE = "testPostCode";
static final String CITY = "testCity";

@Mock
HttpClient httpClient;

@Test
public void whenFetchingWeather() {
    **when(httpClient.toBlocking().exchange(HttpRequest.GET("https://api.weatherapi.com/v1/current.json?key=XXX&q=city&aqi=no"), WeatherResponse.class))
            .thenReturn(createWeatherResponse());**

    WeatherService weatherService = new WeatherService();
    HttpResponse<WeatherResponse> response = weatherService.fetchWeather(HOUSE_NUM, STREET, POST_CODE, CITY);

    assertEquals(HttpStatus.OK, response.getStatus());
}


public static HttpResponse<WeatherResponse> createWeatherResponse() {
    WeatherResponse.Condition condition = new WeatherResponse.Condition();
    condition.setCode("1003");

    WeatherResponse.CurrentWeather currentWeather = new WeatherResponse.CurrentWeather();
    currentWeather.setCondition(condition);
    currentWeather.setWind("3.6");
    currentWeather.setTemperature("2.0");

    WeatherResponse weatherResponse = new WeatherResponse();
    weatherResponse.setCurrentWeather(currentWeather);

    return HttpResponse.ok(weatherResponse);
}

}

1

There are 1 best solutions below

0
knittl On

The way to use the framework is when(mock.method(args)).thenReturn(result) and not when(object.getAnotherObject().getAThirdObject().method(args)).thenReturn(result).

Mockito will always call your methods, it just so happens that a Mockito mock by default doesn't do anything, until stubbed. Without stubbing, it only returns default values from methods (0 for numeric return types, empty collections, empty optionals, …, null for other objects including Strings).

So you need to stub your method toBlocking() to return a mock itself (instead of the default null), which can be stubbed subsequently. You can either do that manually or enable a setting to have your Mockito mock instances automatically answer with nested Mocks.

Manual way:

final BlockingHttpClient blockingClientMock = mock(BlockingHttpClient.class);
when(httpClient.toBlocking()).thenReturn(blockingClientMock);
when(blockingMock.exchange(HttpRequest.GET("https://api.weatherapi.com/v1/current.json?key=XXX&q=city&aqi=no"), WeatherResponse.class))
            .thenReturn(createWeatherResponse());

Via Mockito settings:

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private HttpClient httpClient;

when(httpClient.toBlocking().exchange(HttpRequest.GET("https://api.weatherapi.com/v1/current.json?key=XXX&q=city&aqi=no"), WeatherResponse.class))
            .thenReturn(createWeatherResponse());

But remember: Everytime [sic!] a mock returns a mock a fairy dies

This is stated explicitly in the Mockito docs:

WARNING: This feature should rarely be required for regular clean code! Leave it for legacy code. Mocking a mock to return a mock, to return a mock, (...), to return something meaningful hints at violation of Law of Demeter or mocking a value object (a well known anti-pattern).

Can you not use a fake implementation of HttpClient that's built for being used in tests? If not, you might want to consider wrapping the raw HttpClient in your own class which you can then easily replace with a fake implementation or a stub in your tests.