I am trying to test Spring Boot's Rest controller.
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)
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
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
}
}