Spring 启动单元测试不会 运行。 NullInsteadOfMockException

Spring Boot unit test won't run. NullInsteadOfMockException

我一直在按照 JavaWorld 的 JUnit 5 指南来编写我的测试,但测试不会 运行。异常是 NullInsteadOfMockException。谁能帮我弄清楚我做错了什么? JavaWorld 的指南位于 https://www.javaworld.com/article/3537563/junit-5-tutorial-part-1-unit-testing-with-junit-5-mockito-and-hamcrest.html

错误信息:

org.mockito.exceptions.misusing.NullInsteadOfMockException: 
Argument passed to when() is null!
Example of correct stubbing:
    doThrow(new RuntimeException()).when(mock).someMethod();
Also, if you use @Mock annotation don't miss initMocks()

测试class:

@ExtendWith(MockitoExtension.class)
class ConferenceServiceTest {

    @Autowired
    ConferenceServiceImpl conferenceService;

    @Mock
    ConferenceRepository conferenceRepository;

    @Mock
    ConferenceRoomRepository conferenceRoomRepository;

    Conference conference;
    ConferenceRoom conferenceRoom;
    final Integer MAX_CAPACITY = 5;

    @BeforeEach
    void setUp() {
        LocalDateTime conferenceStartDateTime = LocalDateTime.of(2020, Month.JUNE, 20, 10, 15);
        LocalDateTime conferenceEndDateTime = LocalDateTime.of(2020, Month.JUNE, 20, 11, 15);
        conference = new Conference("conferenceName", conferenceStartDateTime, conferenceEndDateTime);
        conferenceRoom = new ConferenceRoom("testRoomName", "testRoomLocation", MAX_CAPACITY);
        conference.setConferenceRoom(conferenceRoom);
    }

    @Test
    void addConference_alreadyExists() {
        doReturn(conference).when(conferenceService).findConference(conference);

        assertThrows(ConferenceAlreadyExistsException.class, () -> conferenceService.addConference(conference));
    }
}

我的 pom.xml

的 JUnit 和 Mockito 部分
 <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.3.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

服务class代码

@Service
@Slf4j
public class ConferenceServiceImpl implements ConferenceService {

    private ConferenceRepository conferenceRepository;
    private ConferenceRoomRepository conferenceRoomRepository;

    public ConferenceServiceImpl(ConferenceRepository conferenceRepository, ConferenceRoomRepository conferenceRoomRepository) {
        this.conferenceRepository = conferenceRepository;
        this.conferenceRoomRepository = conferenceRoomRepository;
    }

    public String addConference(Conference conference) {

        throw new RuntimeException("Not yet implemented");
    }

    public String cancelConference(Conference conference) {

        throw new RuntimeException("Not yet implemented");
    }

    public String checkConferenceRoomAvailability(Conference conference) {

        throw new RuntimeException("Not yet implemented");
    }

    public Conference findConference(Conference conference) {
        return conferenceRepository.findConferenceByNameAndStartDateAndTimeAndEndDateAndTime(
                conference.getName(), conference.getStartDateAndTime(), conference.getEndDateAndTime());
    }

    public ConferenceRoom findConferenceRoom(ConferenceRoom conferenceRoom) {
        return conferenceRoomRepository.findConferenceRoomByNameAndAndLocation(
                conferenceRoom.getName(), conferenceRoom.getLocation());
    }

}

你自动装配了 conferenceService 所以它不是一个你可以在它的 bravhior 中调整的模拟。使用模拟或间谍(部分模拟)。

因为它也是被测单元,所以您仍然需要将其他模拟注入间谍。

试试这个:

import static org.mockito.Mockito.spy;

@ExtendWith(MockitoExtension.class)
class ConferenceServiceTest {

    @InjectMocks
    ConferenceServiceImpl conferenceService;

    @Mock
    ConferenceRepository conferenceRepository;

    @Mock
    ConferenceRoomRepository conferenceRoomRepository;

    // ... other stuff

    @Test
    void addConference_alreadyExists() {
        // partial mock
        ConferenceServiceImpl spy = spy(conferenceService);

        // mock a specific method of the spy
        doReturn(conference).when(spy).findConference(conference);

        // use the partial mock / spy to call the real method under test that uses the mocked other method.
        assertThrows(ConferenceAlreadyExistsException.class, () -> spy.addConference(conference));
    }
}

记住在尝试设置部分模拟时始终使用间谍而不是调用原始的非间谍对象!

另请注意,此类部分模拟是对非理想架构的暗示,并可能表明混合责任。因此,也许可以考虑将不同的方法提取到它们自己的 class 中,并获得更容易的可测试性。还要记住,通常阅读测试和代码比编写代码要多得多,当你试图弄清楚你到底做了什么尝试设置为测试用例时,部分模拟在 6 个月内更难理解。

而不是

@Autowired
ConferenceServiceImpl conferenceService;

你需要使用

@InjectMocks
ConferenceServiceImpl conferenceService;

因为,当您自动装配一个 bean 时,它是使用 spring 容器中的依赖项创建的。为了本次测试,您希望使用 @Mock.

定义的模拟来创建它

你在这里混合了很多概念:

  1. 仅在 spring 生态系统中使用 @Autowire(由 spring 驱动的真实代码或测试)。这里测试中没有spring,所以不要使用

  2. 在常规单元测试中,您最好自己创建 subject(您将要测试的 class)。


@ExtendWith(MockitoExtension.class)
class ConferenceServiceTest {

    // Note, its not a mock, its not autowired!
    ConferenceServiceImpl conferenceService;

    @Mock
    ConferenceRepository conferenceRepository;

    @Mock
    ConferenceRoomRepository conferenceRoomRepository;

    ....

    @BeforeEach
    void setUp() {
      ... // the code that you already have
      ...
      conferenceService = new ConferenceServiceImpl(conferenceRepository, conferenceRoomRepository);
    }

IMO 在学习 @InjectMocks

等高级内容之前,请确保此设置适合您

Stuck 的回答似乎是解决方案。 "you try to mock behavior of the unit under test (findConference of the service itself). In the guide they only mock behavior of a different unit (the repository instead of the service). While the repository is a mock, the service is not."

代码运行如果我使用

@Mock
ConferenceRepository conferenceRepository;

@Mock
ConferenceRoomRepository conferenceRoomRepository;

@InjectMocks
ConferenceServiceImpl conferenceService;

@Test
    void addConference_alreadyExists() {
        doReturn(conference).when(conferenceRepository).findConferenceByNameAndStartDateAndTimeAndEndDateAndTime(
                conference.getName(), conference.getStartDateAndTime(), conference.getEndDateAndTime());;

        assertThrows(ConferenceAlreadyExistsException.class, () -> conferenceService.addConference(conference));
    }