0

I am trying to test Spring Boot's Rest controller.

Problem:

I am using @MockitoBean annotation for mocking the ContactService.java class. The Rest controller depends on this ContactService.java class.

Following the docs on setting up MockMvc, I have the following code:

public class ContactRestControllerTest {

    private MockMvc mockMvc;

    @MockitoBean
    private ContactService contactService;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
        this.objectMapper = new ObjectMapper();
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(new ContactRestController(contactService))
                .build();
    }

    @Test
    void getMessagesByStatus() throws Exception {
        final String STATUS = "Open";

        String messageListStr = "[ { ... } ]";

        Page<List<ContactMessage>> pagedContactMessageList = // omitted for brevity...
        
        Mockito.when(
                this.contactService.getContactMessages(Mockito.eq(STATUS), Mockito.anyMap())
        ).thenReturn(pagedContactMessageList);

        mockMvc.perform(...); // omitted for brevity
    }
}

The above code throws the NullPointerException:

java.lang.NullPointerException: Cannot invoke "...ContactService.getContactMessages(String, java.util.Map)" because "this.contactService" is null

    at ....ContactRestControllerTest.getMessagesByStatus(ContactRestControllerTest.java:151)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

Question:

Why is contactService null even through it is annotated with @MockitoBean and MockitoAnnotations.openMocks(this); is also called?

If I use the @WebMvcTest(ContactRestController.class) annotation over the test class, the test runs successfully. In this case, I don't even need to call MockitoAnnotations.openMocks(this). Everything just works fine.

If this is the correct way, then why do the docs on setting up MockMvc show a different way and what needs to be done to make that work? What am I missing here?

Spring boot version: 3.4.3

1
  • The documentation you link to for setting up MockMVC is for plain Spring not for Spring Boot. The @WebMvcTest and Spring Boot testing in general adds additional support. Commented Feb 25 at 8:38

3 Answers 3

5

You are mixing tutorials/documentation that you shouldn't be mixing.

TL:DR

If this is the correct way, then why do the docs on setting up MockMvc show a different way and what needs to be done to make that work? What am I missing here?

The documentation you link to for setting up MockMVC is for plain Spring not for Spring Boot. The @WebMvcTest and Spring Boot testing in general adds additional support (like automatic mocking and setting up MockMvc for you).

Longer Explanation:

The @MockitoBean annotation is from Spring and not from Mockito, adding MockitoAnnotations.openMocks(this); or @ExtendsWith(MockitoExtension.class) to your test will not create a mock for that annotation. If you want to you should replace the @MockitoBean with @Mock.

public class ContactRestControllerTest {

    private MockMvc mockMvc;

    @Mock
    private ContactService contactService;

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
        this.objectMapper = new ObjectMapper();
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(new ContactRestController(contactService))
                .build();
    }

Another option is, as you apparently want to use Spring, is to add @ExtendsWith(SpringExtension.class) to your test to make your test identified by the Spring Test Context framework and it will handle the @MockitoBean annotation and create a mock.

@ExtendsWith(SpringExtension.class)
public class ContactRestControllerTest {

    private MockMvc mockMvc;

    @MockitoBean
    private ContactService contactService;

    @BeforeEach
    void setup() {
        this.objectMapper = new ObjectMapper();
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(new ContactRestController(contactService))
                .build();
    }

However as you are using Spring Boot what you really should do is use @WebMvcTest and not do manual setup at all. You let Spring Boot handle the creation of the controller and Mock MVC setup, which yuou can now autowire and it will create a mock for you.

@WebMvcTest(ContactRestController.class)
public class ContactRestControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private ContactService contactService;

    @BeforeEach
    void setup() {
        this.objectMapper = new ObjectMapper();
    }

    @Test
    void getMessagesByStatus() throws Exception {
        final String STATUS = "Open";

        String messageListStr = "[ { ... } ]";

        Page<List<ContactMessage>> pagedContactMessageList = // omitted for brevity...
        
        Mockito.when(
                this.contactService.getContactMessages(Mockito.eq(STATUS), Mockito.anyMap())
        ).thenReturn(pagedContactMessageList);

        mockMvc.perform(...); // omitted for brevity
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

MockitoAnnotations.openMocks(this) is a Mockito class. @MockitoBean is a Spring annotation and must be used in a Spring test. This explains why running the test as @WebMvcTest initializes the MockitoBean.

So if you want a mock object to be added as a bean to your application context, annotate your test class with any of the Spring test annotations, such as @SpringBootTest.

Comments

1

MockitoAnnotations.openMocks(this); initializes the Mockito mocks (such as @Mock). @MockitoBean is a spring annotation is not affected by MockitoAnnotations.openMocks(this);

@WebMvcTest is a Spring annotation, which causes your bean to be mocked when it is applied.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.