ปัญหา Mockito: การเยาะเย้ยกำลังเรียกวิธีการจริง

ฉันมีปัญหากับ Mockito ซึ่งมีการเรียกวิธีจริงในการทดสอบมากกว่าวิธีเยาะเย้ย ค้นหามาหลายชั่วโมงแล้ว แต่ยังไม่พบคำตอบที่เหมาะสม

นี่คือบริการที่ฉันกำลังทดสอบ:

@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>>(){}
        );
    }

}

บริการนี้เรียกคลาส GeoService ต่อไปนี้:

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;
    }
}

กรณีทดสอบของฉันมีลักษณะเช่นนี้:

@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);
    }

ตอนนี้ การเยาะเย้ย DwiApiService ทำงานได้ดีกับกรณีทดสอบนี้และอื่นๆ แต่การเยาะเย้ย GeoService ดูเหมือนจะทำให้เกิดปัญหา เมื่อฉันพยายามรันการทดสอบ มันไม่ได้เรียกใช้เมธอดที่ถูกเยาะเย้ย แต่เป็นเมธอดจริงแทน ข้อผิดพลาดไม่ได้ช่วยอะไรมาก...

[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)

ไม่มีใครรู้ว่าเกิดอะไรขึ้นที่นี่? ใช้เวลาหลายชั่วโมงกับเรื่องนี้และดูเหมือนจะไม่สามารถเข้าใจได้


person soFreshSoClean    schedule 14.06.2020    source แหล่งที่มา
comment
ลองเชื่อมต่ออัตโนมัติ geoService แทนที่จะเริ่มต้นด้วย new   -  person Smile    schedule 14.06.2020
comment
พยายามแล้วได้รับข้อผิดพลาดเดียวกัน   -  person soFreshSoClean    schedule 14.06.2020
comment
สิ่งนี้ตอบคำถามของคุณหรือไม่? Mockito - ความแตกต่างระหว่าง doReturn() และ when()   -  person Progman    schedule 14.06.2020
comment
@Progman พยายามแล้ว แต่ดูเหมือนจะไม่ทำงาน Stepio ด้านล่างพบคำตอบที่ใช้งานได้ ไม่แน่ใจเลยว่าทำไม!   -  person soFreshSoClean    schedule 14.06.2020


คำตอบ (2)


ปัญหาที่เป็นไปได้คือคุณระบุพารามิเตอร์ที่แน่นอน พิจารณาการกำหนดค่า mockito เป็นชุดของกฎ ดังนั้นค่าที่แน่นอนจะไม่ทำงานที่นั่น (เว้นแต่คุณจะโชคดีจริงๆ และมันก็ใช้งานได้อย่างน่าอัศจรรย์สำหรับกรณีที่แคบบางกรณีโดยเฉพาะ)

ดังนั้นลองแทนที่สิ่งนี้:

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

ด้วยสิ่งนี้:

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

นอกจากนี้ บางครั้งฉันก็มีปัญหาในการใช้โครงสร้าง when(...).thenReturn(...) - มันทำงานไม่ถูกต้องกับ @Spy เป็นต้น ฉันมักจะชอบวิธีนี้แทน:

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

และหากพารามิเตอร์บางตัวไม่สำคัญสำหรับคุณจริงๆ (หรือทั้งหมด) อย่าใช้ eq(...) และอย่าใช้ค่าใดๆ เพียงแทนที่พารามิเตอร์ที่เกี่ยวข้องด้วย any()

ป.ล. ฉันเปลี่ยนกฎล้อเลียนเพียงข้อเดียว โดยสมมติว่าคุณจะตรวจสอบผู้อื่นด้วยตนเอง

person stepio    schedule 14.06.2020
comment
อืม ฉันแทนที่ when(geoService.isLocationWithinDistance(distanceInMiles, cityLatt, cityLong, person)).thenReturn(true); ด้วย when(geoService.isLocationWithinDistance(anyDouble(), anyDouble(), anyDouble(), any())).thenReturn(true); แล้วมันก็ได้ผล! ไม่แน่ใจว่าทำไม ดังนั้นจะเจาะลึกลงไปอีกสักหน่อย ขอบคุณ! - person soFreshSoClean; 14.06.2020

สาเหตุอาจเป็นเพราะคุณเป็นอินสแตนซ์โดยตรง

private GeoService geoService = new GeoService();

คุณอาจต้องใส่คำอธิบายประกอบคลาส GeoService ด้วย @Service และทำการเชื่อมต่ออัตโนมัติบน DwpApiService

person Hamza    schedule 14.06.2020
comment
เพิ่มคำอธิบายประกอบ @Service ไปยังคลาส GeoService และ @Autowired ไปยังการอ้างอิง GeoService ในคลาส DwiApiService ยังคงได้รับข้อผิดพลาดเดียวกัน - person soFreshSoClean; 14.06.2020