简体   繁体   English

Spring 引导单元测试不会运行。 NullInsteadOfMockException

[英]Spring Boot unit test won't run. NullInsteadOfMockException

I've been following JavaWorld's JUnit 5 Guide to write my tests but the tests won't run.我一直在按照 JavaWorld 的 JUnit 5 指南编写测试,但测试无法运行。 The exception is NullInsteadOfMockException.例外是 NullInsteadOfMockException。 Can anyone help me figure out what I'm doing wrong?谁能帮我弄清楚我做错了什么? JavaWorld's guide is at https://www.javaworld.com/article/3537563/junit-5-tutorial-part-1-unit-testing-with-junit-5-mockito-and-hamcrest.html JavaWorld 的指南位于https://www.javaworld.com/article/3537563/junit-5-tutorial-part-1-unit-testing-with-junit-5-mockito-and-hamcrest.html

Error message:错误信息:

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

Test class:测试 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));
    }
}

JUnit and Mockito part of my pom.xml JUnit 和 Mockito 我的 pom.xml 的一部分

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

Service class code服务 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());
    }

}

Instead of代替

@Autowired
ConferenceServiceImpl conferenceService;

you need to use你需要使用

@InjectMocks
ConferenceServiceImpl conferenceService;

Because, when you Autowire a bean, it gets created with the dependencies from the spring container.因为,当您自动装配一个 bean 时,它是使用来自 spring 容器的依赖项创建的。 In the interest of this test, you want it to be created with mocks which are defined by @Mock .为了这个测试的利益,您希望使用@Mock定义的模拟来创建它。

You mix here many concepts:您在这里混合了许多概念:

  1. Use @Autowire only in spring ecosystem (real code or test driven by spring).仅在 spring 生态系统中使用@Autowire (实际代码或由 spring 驱动的测试)。 Here you don't have spring in the test, therefor don't use it.这里你没有 spring 在测试中,所以不要使用它。

  2. In a regular unit test you better create the subject (class that you're about to test) by yourself.在常规单元测试中,您最好自己创建主题(您将要测试的类)。


@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 make sure that this setup work for you before learning advanced stuff like @InjectMocks在学习诸如@InjectMocks类的高级内容之前,IMO 确保此设置适合您

You autowired conferenceService so it is not a mock that you could adjust in its bravhior.您自动装配了conferenceService ,因此它不是一个您可以对其进行调整的模拟。 Either use a mock or a spy (partial mock).使用模拟或间谍(部分模拟)。

Because it is also the unit under test you still need to inject the other mocks into the spy.因为它也是被测单元,所以您仍然需要将其他模拟注入间谍。

Try this:尝试这个:

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

Remember to always use the spy and not calling the original non-spyed object when trying to setup a partial mock!记住在尝试设置部分模拟时始终使用间谍而不是调用原始的非间谍 object!

Also note that such partial mocks are a hint for a non-ideal architecture and potentially indicate mixed responisbilities.另请注意,此类部分模拟是对非理想架构的暗示,并可能表明混合责任。 So maybe consider extracting the different methods to their own class and gain easier testability.所以也许考虑将不同的方法提取到他们自己的 class 并获得更容易的可测试性。 Also remember that tests and code in general are much more often read than written and partial mocks are harder to comprehend in 6 month when you try to figure out what the heck you did try to setup as test case.还要记住,一般来说,测试和代码的阅读频率要比编写的频率高得多,当你试图弄清楚你到底尝试将什么设置为测试用例时,部分模拟在 6 个月内更难理解。

Stuck's answer seems to be the solution.卡住的答案似乎是解决方案。 "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." “您尝试模拟被测单元的行为(findConference 服务本身)。在指南中,他们只模拟不同单元的行为(存储库而不是服务)。虽然存储库是模拟的,但服务不是。”

The code runs if I use如果我使用代码运行

@Mock
ConferenceRepository conferenceRepository;

@Mock
ConferenceRoomRepository conferenceRoomRepository;

@InjectMocks
ConferenceServiceImpl conferenceService;

and

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM