soFreshSoClean
soFreshSoClean

Reputation: 57

Mockito issue: mock is calling actual method

I'm having a problem with Mockito where the real method is being called in the test rather than the mocked method. Searched for hours but haven't been able to find a suitable answer.

Here's the service I'm testing:

@Service
public class DwpApiService {

    @Autowired
    private RestTemplate restTemplate = new RestTemplate();

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    private GeoService geoService = new GeoService();

    private String baseUri = "XXXX";

    private double cityLatt = 51.5074;
    private double cityLong = -0.1278;

    public List<PersonApiModel> getAllUsersInCityOrWithinDistanceOfCity(String cityName, double distanceInMiles) throws RuntimeException {

        HashMap<Integer, PersonApiModel> usersInCityOrWithinDistance = new HashMap<>();

        List<PersonApiModel> usersInCity = getAllUsersInCity(cityName).getBody();

        for (PersonApiModel person: usersInCity) {
            usersInCityOrWithinDistance.put(person.getId(), person);
        }

        List<PersonApiModel> allUsers = getAllUsers().getBody();

        for (PersonApiModel person : allUsers) {
            boolean withinDistance = geoService.isLocationWithinDistance(distanceInMiles, cityLatt, cityLong, person);
            if (withinDistance && !usersInCityOrWithinDistance.containsKey(person.getId())) {
                usersInCityOrWithinDistance.put(person.getId(), person);
            }
        }

        return new ArrayList<>(usersInCityOrWithinDistance.values());
    }

    public ResponseEntity<List<PersonApiModel>> getAllUsersInCity(String cityName) throws RuntimeException {
        String cap = cityName.substring(0, 1).toUpperCase() + cityName.substring((1));

        if (!cap.equals("London")) {
            throw new IllegalArgumentException("Invalid city name. Please only use the city of London.");
        }

        return restTemplate.exchange(
                baseUri + "city/" + cap + "/users",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<PersonApiModel>>(){}
        );
    }

    public ResponseEntity<List<PersonApiModel>> getAllUsers() throws RuntimeException {
        return restTemplate.exchange(
                baseUri + "/users",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<PersonApiModel>>(){}
        );
    }

}

This service calls the following GeoService class:

public class GeoService {

    //taken from https://www.movable-type.co.uk/scripts/latlong.html

    public boolean isLocationWithinDistance(double radiusInMiles,
                                            double sourceLat, double sourceLong,
                                            PersonApiModel personApiModel) throws IllegalArgumentException {

        boolean isLatLongWithinRange = checkIfLatLongAreWithinRange(
                sourceLat, sourceLong,
                personApiModel.getLatitude(), personApiModel.getLongitude());

        if (!isLatLongWithinRange) {
            throw new IllegalArgumentException("Latitude or Longitude are not valid values for id=" + personApiModel.getId());
        }

        int earthMeanRadius = 6371;

        double latDiff = Math.toRadians(sourceLat -  personApiModel.getLatitude());
        double longDiff = Math.toRadians(sourceLong - personApiModel.getLongitude());
        double a = Math.sin(latDiff / 2) * Math.sin(latDiff / 2)
                + Math.cos(Math.toRadians(sourceLat)) * Math.cos(Math.toRadians(personApiModel.getLatitude()))
                * Math.sin(longDiff / 2) * Math.sin(longDiff / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double distance = earthMeanRadius * c;

        double radiusInKm = radiusInMiles * 1.609344;

        return radiusInKm >= distance;
    }

    public boolean checkIfLatLongAreWithinRange(double sourceLat, double sourceLong,
                                                   double destinationLat, double destinationLong) {
        boolean sourceLatInRange = Math.abs(sourceLat) <= 90;
        boolean destLatInRange = Math.abs(destinationLat) <= 90;
        boolean sourceLongLessThanMinus180 = sourceLong >= -180;
        boolean sourceLongLessThan80 = sourceLong <= 80;
        boolean destLongLessThanMinus180 = destinationLong >= -180;
        boolean destLongLessThan80 = destinationLong <= 80;

        return sourceLatInRange && destLatInRange
                && sourceLongLessThanMinus180 && sourceLongLessThan80
                && destLongLessThanMinus180 && destLongLessThan80;
    }
}

My test case looks like this:

@RunWith(MockitoJUnitRunner.class)
public class DwpApiServiceTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private GeoService geoService;

    @InjectMocks
    private final DwpApiService dwpApiService = new DwpApiService();

    private final PersonApiModel personApiModel1 = new PersonApiModel();
    private final PersonApiModel personApiModel2 = new PersonApiModel();

    private final List<PersonApiModel> fakeList1 = new ArrayList<>();
    private final List<PersonApiModel> fakeList2 = new ArrayList<>();

    private final String baseUri = "XXXXXX";
    private double cityLatt = 51.5074;
    private double cityLong = -0.1278;

  @Test
    public void givenMocks_whenGetAllUsersInAndAroundCity_returnMockedPersonList() {
        personApiModel1.setId(1);
        fakeList1.add(personApiModel1);
        ResponseEntity<List<PersonApiModel>> expected1 = new ResponseEntity<>(fakeList1, HttpStatus.OK);

        personApiModel2.setId(2);
        fakeList2.add(personApiModel2);
        ResponseEntity<List<PersonApiModel>> expected2 = new ResponseEntity<>(fakeList2, HttpStatus.OK);

        PersonApiModel person = personApiModel2;

        List<PersonApiModel> expected = new ArrayList<>();
        expected.addAll(fakeList1);
        expected.addAll(fakeList2);

        String cityName = "london";
        double distanceInMiles = 20;

        Mockito.when(dwpApiService.getAllUsers())
                .thenReturn(expected1);

        Mockito.when(dwpApiService.getAllUsersInCity(cityName))
                .thenReturn(expected2);

        Mockito.when(geoService.isLocationWithinDistance(distanceInMiles, cityLatt, cityLong, person))
                .thenReturn(true);

        List<PersonApiModel> actual = dwpApiService.getAllUsersInCityOrWithinDistanceOfCity(cityName, distanceInMiles);

        Assert.assertEquals(expected, actual);
    }

Now, mocking the DwiApiService works fine, with this test case and others. But mocking the GeoService seems to cause issue. When I try to run the test, it does not actually invoke the mocked method but the real one instead. The errors aren't very helpful...

[MockitoHint] DwpApiServiceTest.givenMocks_whenGetAllUsersInAndAroundCity_returnMockedPersonList (see javadoc for MockitoHint):
[MockitoHint] 1. Unused... -> at com.dwpAPI.services.DwpApiServiceTest.givenMocks_whenGetAllUsersInAndAroundCity_returnMockedPersonList(DwpApiServiceTest.java:115)
[MockitoHint]  ...args ok? -> at com.dwpAPI.services.DwpApiService.getAllUsersInCityOrWithinDistanceOfCity(DwpApiService.java:47)

Does anybody know what's going on here? Spending hours and hours on this and just can't seem to figure it out.

Upvotes: 4

Views: 1984

Answers (2)

stepio
stepio

Reputation: 905

Possible issue is that you specify exact parameters. Consider mockito configuration as set of rules, so exact values don't work there (unless you're really lucky and it just magically works for some specific narrow case).

So try replacing this one:

when(geoService.isLocationWithinDistance(distanceInMiles, cityLatt, cityLong, person))
    .thenReturn(true);

With this:

when(geoService.isLocationWithinDistance(eq(distanceInMiles), eq(cityLatt), eq(cityLong), eq(person)))
    .thenReturn(true);

Also I sometimes have issues using when(...).thenReturn(...) construction - it does not work properly with @Spy, for example. So I usually prefer this approach instead:

doReturn(true).when(geoService)
    .isLocationWithinDistance(eq(distanceInMiles), eq(cityLatt), eq(cityLong), eq(person));

And if some of the parameters is not really important for you (or all of them), don't use eq(...) and don't use any value - just replace the relevant parameter with any().

P.S.: I changed just a single mocked rule, assuming that you'll review others yourself.

Upvotes: 1

DEV
DEV

Reputation: 1726

the reason may be cause u are directly instanciance it

private GeoService geoService = new GeoService();

you probably need to annotated the class GeoService with @Service and autowired it on DwpApiService

Upvotes: 0

Related Questions