@RunWith(MockitoJUnitRunner.class) 模拟为空

@RunWith(MockitoJUnitRunner.class) mocks are null

我正在使用“RunWith(MockitoJUnitRunner.class)”、@Mock notationt 来模拟服务,并使用@InjectMocks notation 将模拟服务注入控制器。我在“何时”的 ChargingStationsControllerTest:40 中得到 NullPointerException。我调试并意识到模拟是空的。 这是我的代码:

package com.example.assignmentchargingstations.controllers;


import com.example.assignment.chargingStations.controllers.ChargingStationsController;
import com.example.assignment.chargingStations.models.AddressInfo;
import com.example.assignment.chargingStations.models.ChargingStation;
import com.example.assignment.chargingStations.models.StatusType;
import com.example.assignment.chargingStations.services.OpenChargeMapService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.ArrayList;
import java.util.List;

import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class ChargingStationsControllerTest {

    @Mock
    private OpenChargeMapService openChargeMapService;
    @InjectMocks
    private ChargingStationsController chargingStationsController;

    @Test
    public void testGetNearestChargingStations() throws Exception {
        Double latitude = 90.0;
        Double longitude = 90.0;
        List<ChargingStation> chargingStationsListExpected = new ArrayList<>();
        StatusType statusTypeExpected = StatusType.builder().IsOperational(true).build();
        AddressInfo addressInfoExpected = AddressInfo.builder().Latitude(latitude).Longitude(longitude).Title("Test Charging Station").build();
        chargingStationsListExpected.add(ChargingStation.builder().StatusType(statusTypeExpected).AddressInfo(addressInfoExpected).build());
        when(openChargeMapService.getNearestChargingStations(anyDouble(), anyDouble())).thenReturn(chargingStationsListExpected);
        ResponseEntity<List<ChargingStation>> responseExpected = chargingStationsController.getNearestChargingStations(latitude, longitude);
        Assertions.assertEquals(responseExpected.getStatusCode(), HttpStatus.OK);
        Assertions.assertEquals(responseExpected.getBody(), chargingStationsListExpected);
    }
}

我正在测试的控制器:

package com.example.assignment.chargingStations.controllers;


import com.example.assignment.chargingStations.models.ChargingStation;
import com.example.assignment.chargingStations.services.OpenChargeMapService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
public class ChargingStationsController {

    private final OpenChargeMapService openChargeMapService;

    public ChargingStationsController(OpenChargeMapService openChargeMapService) {
        this.openChargeMapService = openChargeMapService;
    }

    @RequestMapping(path = "/nearest-charging-stations", method = RequestMethod.GET)
    public ResponseEntity<List<ChargingStation>> getNearestChargingStations(@RequestParam Double latitude, @RequestParam Double longitude) {
        List<ChargingStation> nearestChargingStations = openChargeMapService.getNearestChargingStations(latitude, longitude);
        return new ResponseEntity<>(nearestChargingStations, HttpStatus.OK);
    }
}

服务:

package com.example.assignment.chargingStations.services;

import com.example.assignment.chargingStations.clients.OpenChargeMapClient;
import com.example.assignment.chargingStations.models.ChargingStation;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;

@Service
public class OpenChargeMapService {

    private static final Double MAX_LATITUDE = 90.0000000;
    private static final Double MIN_LATITUDE = -90.0000000;
    private static final Double MAX_LONGITUDE = 180.0000000;
    private static final Double MIN_LONGITUDE = -180.0000000;
    private final OpenChargeMapClient openChargeMapClient;

    public OpenChargeMapService(OpenChargeMapClient openChargeMapClient) {
        this.openChargeMapClient = openChargeMapClient;
    }

    public List<ChargingStation> getNearestChargingStations(Double latitude, Double longitude) {
        validateLatitudeLongitude(latitude, longitude);
        List<ChargingStation> chargingStationsList = openChargeMapClient.getNearestChargingStations(latitude, longitude);
        return chargingStationsList;
    }

    private void validateLatitudeLongitude(Double latitude, Double longitude) {
        if (isLatitudeOutOfRange(latitude) || isLongitudeOutOfRange(longitude)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Values for latitude and longitude are out of range.");
        }
    }

    private boolean isLatitudeOutOfRange(Double latitude) {
        return (latitude.compareTo(MIN_LATITUDE) < 0) || (latitude.compareTo(MAX_LATITUDE) > 0);
    }

    private boolean isLongitudeOutOfRange(Double longitude) {
        return (longitude.compareTo(MIN_LONGITUDE) < 0) || (longitude.compareTo(MAX_LONGITUDE) > 0);
    }
}

build.gradle:

plugins {
    id 'org.springframework.boot' version '2.5.1'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.projectlombok:lombok:1.18.18'
    implementation 'junit:junit:4.13.1'
    annotationProcessor 'org.projectlombok:lombok:1.18.18'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

因为您使用的是 Spring Boot.你有两个选择。您可以单独测试 class,也可以在 spring 上下文中加载。

这两种方法都有其优点和局限性。

将模拟直接提供给测试中的 class。

public class Test {
   ClassToMock service = mock(ClassToMock.class)
   
   ClassInTest testService = new ClassInTest(service);
   
   @Test
   public void doTest() {
      ....
   }
}

通过这种方法,您可以直接向测试中的服务提供模拟。这将导致更快的运行时间,但您将无法进行更全面的集成测试。

将模拟服务直接加载到 spring 上下文中

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class Test {
   @MockBean
   ClassToMock service;
   
   @Autowired
   ClassInTest testService;
   
   @Test
   public void doTest() {
      ....
   }
}

这将生成 spring 上下文并模拟您创建的服务组件。这样做会测试 bean 的连接和测试中的服务。如果您想模拟封装服务(即存储库 layer/data 层),您还将拥有更大的灵活性。您还应该能够将 @Mock 和 @InjectMock 与您当前正在使用的 class 注释一起使用。