I am writing end to end tests for many different Spring Controllers. Right now, I am trying to write a general class for testing that contains MockMvc performing methods. I have endpoints that I need to call in different Controllers and I do not want to copy paste code around, and have a MockMvc and ObjectMapper in each test class.
Few examples of the methods:
public void saveMockDish(DishDto dishDto) throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post(DISH_ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dishDto)))
.andExpect(status().isCreated());
}
public DishDto getMockDish(Long id) throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
.get(DISH_ENDPOINT "/{id}", id))
.andExpect(status().isOk())
.andReturn();
String contentAsString = mvcResult.getResponse().getContentAsString();
return objectMapper.readValue(contentAsString, new TypeReference<>() {
});
}
What I am trying to accomplish (a bean that I could autowire in another class, example in DishControllerTest class):
@AutoConfigureMockMvc
@TestComponent
public class AppMockMcv {
private static final String DISH_ENDPOINT = "/dishes";
private static final String BASE_INGREDIENTS_ENDPOINT = "/base-ingredients";
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
public List<DishDto> getMockDishes() throws Exception {
...
How I want to instantiate my test classes:
@SpringBootTest
public class DishControllerTest {
@Autowired
private AppMockMcv appMockMcv;
@Test
void testGetDishes() throws Exception {
List<DishDto> dishes = appMockMcv.getMockDishes();
assertEquals(4, dishes.size());
assertNotNull(dishes);
DishAssertions.containsDish(dishes, "Pasta Carbonara");
}
Right now I am facing the issue that I can not use @AutoConfigureMockMvc together with @TestComponent, the bean is not found in autowiring. I also tried @Component, @TestConfiguration, @SpringBootTest annotations in the AppMockMcv class.
Current error, although not very useful:
No qualifying bean of type 'mtv.restaurant.mock.AppMockMcv' available:
expected at least 1bean which qualifies as autowire candidate.
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)
I did not find much information on how to create a bean for testing only, also on how to combine it with AutoConfigureMockMvc. Also, I tried to find a way on how to extend MockMvc without success.
What is the right way to achieve what I am trying to accomplish?
- Spring boot 2.5.4
CodePudding user response:
Heh, after I started learning Spring Boot I tend to forget basic Java...
The solution I came up is that I @Autowire and @AutoConfigureMockMvc in the xControllerTest class and create an instance of AppMockMcv @BeforeAll tests, where I pass in MockMvc and ObjectMapper.
That way I can invoke my requests and only have the Mvc performing methods in one class.
I will share my solution, but I would be very grateful if someone shared a solution on how it should be done properly.
AppMockMcv:
public class AppMockMcv {
private static final String DISH_ENDPOINT = "/dishes";
private static final String BASE_INGREDIENTS_ENDPOINT = "/base-ingredients";
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
public AppMockMcv(MockMvc mockMvc, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
this.objectMapper = objectMapper;
}
public List<DishDto> getMockDishes() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
.get(DISH_ENDPOINT)
.queryParam("custom", "false"))
.andExpect(status().isOk())
.andReturn();
String contentAsString = mvcResult.getResponse().getContentAsString();
return objectMapper.readValue(contentAsString, new TypeReference<>() {
});
}
}
DishControllerTest:
@AutoConfigureMockMvc
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class DishControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
private AppMockMcv appMockMcv;
@BeforeAll
public void setup() {
appMockMcv = new AppMockMcv(mockMvc, objectMapper);
}
@Test
void testGetDishes() throws Exception {
List<DishDto> dishes = appMockMcv.getMockDishes();
assertEquals(4, dishes.size());
assertNotNull(dishes);
DishAssertions.containsDish(dishes, "Pasta Carbonara");
}
}
CodePudding user response:
I did not find much information on how to create a bean for testing only, also on how to combine it with AutoConfigureMockMvc. Also, I tried to find a way on how to extend MockMvc without success.
If you create bean under /test/java, then it will only be used in tests(in most cases), even if the component annotation is used. Check the information here - https://docs.spring.io/spring-boot/docs/1.4.3.RELEASE/reference/html/boot-features-testing.html (40.3.2).
Also, I wrote little code for example, it works (Because you didn't provide configuration for Spring, I used only @SpringBootApplication):
package src:
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
package src.test:
@Component
public class CustomMockMVc {
public MockMvc getMockMvc() {
return mockMvc;
}
private final MockMvc mockMvc;
public CustomMockMVc(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
}
@SpringBootTest
@AutoConfigureMockMvc
public class TestComponent {
@Autowired
private CustomMockMVc customMockMVc;
@Test
public void testMockMvc() {
System.out.println(customMockMVc.getMockMvc());
}
}