WebApplicationContext is required - MockMVC - java

I'm trying to unit test a controller class, but have been stuck with the following error. I tried changing notations and following some online tutorials but it has not been working, I always get this same error.
Here's the stackTrace:
java.lang.IllegalArgumentException: WebApplicationContext is required
at org.springframework.util.Assert.notNull(Assert.java:201)
at org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder.<init>(DefaultMockMvcBuilder.java:52)
at org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup(MockMvcBuilders.java:51)
at br.com.gwcloud.smartplace.catalog.controller.test.ItemControllerTest.setUp(ItemControllerTest.java:66)
...
And this is my controller test class:
#SpringBootTest
#WebMvcTest(controllers = ItemController.class)
#ActiveProfiles("test")
#WebAppConfiguration
public class ItemControllerTest {
#MockBean
private ItemRepository ir;
#Autowired
private MockMvc mockMvc;
#Autowired
private ModelMapper modelMapper;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private ObjectMapper objectMapper;
#Before
public void setUp() {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.webApplicationContext);
this.mockMvc = builder.build();
}
#Test
public void shouldCreateNewItem() throws Exception {
ItemDTO itemDTO = new ItemDTO();
itemDTO.setName("Leo");
itemDTO.setDescription("abc description");
itemDTO.setEnabled(true);
itemDTO.setPartNumber("leo123");
Item item = itemDTO.convertToEntity(modelMapper);
mockMvc.perform(
post("/api/item/").contentType(
MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(item))).andExpect(
status().isOk());
}
}

The error you are encountering might be solved by adding:
#RunWith(SpringRunner.class)
Just below #SpringBootTest. Or you could instead extend AbstractJUnit4SpringContextTests.
Another problem is that the WebApplicationContext might not be available in an #Before annotated method. Try moving it into the test method itself.
That said, I usually avoid unit testing controllers, since I don't put any business logic in them. The stuff controllers do is specifying paths, mapping request arguments, error handling (although that is better handled in a separate ControllerAdvice class), setting up the view model and view target etc. These are all what I call 'plumbing' and tie in heavily with the framework you are using. I don't unit test that.
Instead, this plumbing can be validated by having a couple of high level integration tests that actually do a remote call to the controller and execute a complete flow, including all the plumbing.
Any business logic should be taken outside of the controller (typically in services) and unit tested there, in isolation.

Have you tried removing #SpringBootTest and #WebAppConfiguration. If you are only interested in testing controller you don't need a full-blown application through these annotations.

Related

Spring Integration testing with MockMvc - service injection

I'm trying to make an integration test going all the way from the top (controller) down to the repositories (mocked). I'm having issue with getting an actual service (not mock) injected into a controller and mocking the service's repositories. Most examples I've seen either call the endpoint via MockMvc and test the controller that contains all the logic, so no services, or they just mock the service and return a set response which I see no point in testing at all. My classes work as such: Controller contains the service which contains the repository
Currently I have the following test class for that:
#WebMvcTest(PatientRecordController.class)
public class PatientRecordControllerTests {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper mapper;
#MockBean
PatientRecordService patientRecordService;
#Test
public void createRecord_WhenDtoValid_CreateRecord() throws Exception {
PatientRecordDto recordDto = PatientRecordDto.builder()
.name("John Doe")
.age(47)
.address("New York USA")
.build();
PatientRecord record = ModelMapperUtil.convertTo(recordDto, PatientRecord.class);
Mockito.when(patientRecordService.saveRecord(recordDto)).thenReturn(record);
MockHttpServletRequestBuilder mockRequest = MockMvcRequestBuilders.post("/patient")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(this.mapper.writeValueAsString(recordDto));
mockMvc.perform(mockRequest)
.andExpect(status().isOk())
.andExpect(jsonPath("$", notNullValue()))
.andExpect(jsonPath("$.name", is("John Doe")));
}
}
What I would like to be able to do is something akin to following:
#Autowired
#InjectMocks
private PatientRecordService patientRecordService;
#Mock
private PatientRecordRepository patientRecordRepository;
Theoretically, with this I'd get a bean of PatientRecordService that has PatientRecordRepository mock injected, and then instead of doing
Mockito.when(patientRecordService.saveRecord(recordDto)).thenReturn(record);
I could do
Mockito.when(patientRecordRepository.save(recordDto)).thenReturn(record);
And then the whole logic inside of the service would be tested and only repository response would be mocked. Obviously this is not how it works, so how would I go about in achieving this?
You should be able to
#WebMvcTest({PatientRecordController.class, PatientRecordService.class})
#MockBean
private PatientRecordRepository patientRecordRepository;
Does that not achieve what you need?

#WebMvcTest creating more than one Controller for some reason

I'm trying to create a controller test with #WebMvcTest, and as I understand, when I put #WebMvcTest(ClientController.class) annotation of the test class it should not create a whole lot of beans, but just ones that this controller requires.
I'm mocking the bean this controller requires with #MockBean, but somehow it fails with an exception that there's 'No qualifying bean' of another service that does not required by this controller but by another.
So this test is failing:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = ClientController.class)
public class ClientControllerTest {
#MockBean
ClientService clientService;
#Test
public void getClient() {
assertEquals(1,1);
}
}
I've created an empty Spring Boot project of the same version (2.0.1) and tried to create test over there. It worked perfectly.
So my problem might be because of the dependencies that my project has many, but maybe there's some common practice where to look in this situation? What can mess #WebMvcTest logic?
I've found a workaround. Not to use #WebMvcTest and #MockBean, but to create everything by hand:
//#WebMvcTest(ClientController.class)
#RunWith(SpringRunner.class)
public class ClientControllerTest {
private MockMvc mockMvc;
#Mock
ClientService clientService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(
new ClientController(clientService)
).build();
}
works with Spring 1.4.X and with Spring Boot 2.X (had different exception there and there), but still doesn't explain why #WebMvcTest doesn't work

Testing a custom RepositoryRestController that uses a PersistentEntityResourceAssembler

I have a RepositoryRestController that exposes resources for some persistent entities.
I have a method on my controller that takes a PersistentEntityResourceAssembler to help me generate the resources automatically.
#RepositoryRestController
#ExposesResourceFor(Customer.class)
#RequestMapping("/api/customers")
public class CustomerController {
#Autowired
private CustomerService service;
#RequestMapping(method = GET, value="current")
public ResponseEntity getCurrent(Principal principal Long id, PersistentEntityResourceAssembler assembler) {
return ResponseEntity.ok(assembler.toResource(service.getForPrincipal(principal)));
}
}
(Contrived example, but it saves going into too much detail about irrelevant details of my use-case)
I'd like to write a test for my controller (my real use-case is actually worth testing), and am planning on making use of #WebMvcTest.
So I have the following test class:
#RunWith(SpringRunner.class)
#WebMvcTest(CustomerController.class)
#AutoConfigureMockMvc(secure=false)
public class CustomerControllerTest {
#Autowired
private MockMvc client;
#MockBean
private CustomerService service;
#Test
public void testSomething() {
// test stuff in here
}
#Configuration
#Import(CustomerController.class)
static class Config {
}
}
But I get an exception saying java.lang.NoSuchMethodException: org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.<init>()
Presumably something is not being configured correctly here because I'm missing the entire data layer. Is there some way of mocking out the PersistentEntityResourceAssembler? Or another approach I could use here?
I ended up for now with:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
The downsite of it is that the test would start the full Spring application context (but without the server).
I ended up doing a slightly hacky solution here:
I removed PersistentEntityResourceAssembler from the controller method.
I added an #Autowired RepositoryEntityLinks to the controller, on which I call linkToSingleResource to create the links as needed.
I added an #MockBean RepositoryEntityLinks to my test class, and configured the mocking to return something sensible:
given(repositoryEntityLinks.linkToSingleResource(any(Identifiable.class)))
.willAnswer(invocation -> {
final Identifiable identifiable = (Identifiable) invocation.getArguments()[0];
return new Link("/data/entity/" + identifiable.getId().toString());
});
It's far from ideal - I'd love to know if there's a way of getting just enough of the data layer up that I can depend on PersistentEntityResourceAssembler.

Remove exception with bean creation on integration test, which does not appear on standard application start

I have spring boot application which works well, but when I started to work on integration tests, I discovered, that there is cyclic dependency in project:
#Service
public class CrowdManagerSyncService {
private final CrowdManagerSyncScheduler crowdManagerSyncScheduler;
#Autowired
public CrowdManagerSyncService(CrowdManagerSyncScheduler crowdManagerSyncScheduler) {
this.crowdManagerSyncScheduler = Objects.requireNonNull(crowdManagerSyncScheduler);
}
}
And
#Component
public class CrowdManagerSyncScheduler {
#Autowired
private CrowdManagerSyncService crowdManagerSyncService;
}
It is not my code and I am not ready to rewrite it right now. But it works perfectly well in production. In my integration test
#RunWith(SpringRunner.class)
#WebMvcTest(UserController.class)
#WithMockUser(roles={"ADMIN"})
#ContextConfiguration(classes = {AdminConsoleApplication.class, DataSourceAutoConfiguration.class,
MockMvcAutoConfiguration.class, MockMvcWebDriverAutoConfiguration.class})
public class UserControllerTest {
#Autowired
private MockMvcHtmlUnitDriverBuilder builder;
private WebDriver webDriver;
#Before
public void setUp() throws Exception {
webDriver = builder.build();
}
}
I catch exception:
Error creating bean with name 'crowdManagerSyncService': Requested bean is currently in creation: Is there an unresolvable circular reference?
So, my question is: how to omit this problem in testing without removing that awful circular dependency? It works well in production, so pretty sure there is some way to start test context without code change.
#WebMvcTest is not suitable for "proper" integration tests.
From the api docs:
Can be used when a test focuses only on Spring MVC components.
However, you're then using #ContextConfiguration to essentially add your whole application to the test.
Remove the #ContextConfiguration and instead autowire a #MockBean CrowdManagerSyncService into your test.
This creates a mock version of CrowdManagerSyncService and injects it into the UserController in the test application context.
#RunWith(SpringRunner.class)
#WebMvcTest(UserController.class)
#WithMockUser(roles={"ADMIN"})
public class UserControllerTest {
#Autowired
private MockMvcHtmlUnitDriverBuilder builder;
#MockBean
private CrowdManagerSyncService service;
private WebDriver webDriver;
#Before
public void setUp() throws Exception {
webDriver = builder.build();
}
#Test
public void shouldWork() {
when(service.doStuff())
.thenReturn("Hello"); // regular Mockito mocking
}
}
This is appropriate if you're just trying to test the UserController and sidesteps the circular dependency problem because there's no instantiation of a "real" CrowdManagerSyncService anywhere.
You can also replace #WebMvcTest and #ContextConfiguration with both #SpringBootTest (which bootstraps the application just like production) and #AutoConfigureMockMvc (which replaces the real HTTP stuff with MockMvc).

Spring MVC Controller testing, and mocking many classes

We have many Controllers in our system, and many Spring Data repositories.
I would like to write tests for my controllers that run through my MVC context.
However, it seems pretty cumbersome, and just not right, to have to, by hand, mock every service and repository in my system, so that I can test the controllers
e.g.
FooControllerTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy(value = {
#ContextConfiguration(classes = { MockServices.class }),
#ContextConfiguration({ "classpath:/META-INF/spring/mvc-servlet-context.xml" }),
})
public class FooControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mvc;
#Autowired
private FooRepository fooRepository;
#Autowired
private FooService fooService;
#Before
public void setUp() throws Exception {
mvc = webAppContextSetup(wac).build();
}
#Test
public final void list() {
when(fooRepository.findAll()).thenReturn(...);
mvc.perform(get("/foo"))...
}
#Test
public final void create() {
Foo fixture = ...
when(fooService.create(fixture)).thenReturn(...);
mvc.perform(post("/foo"))...
}
}
MockServices.java
#Configuration
public class MockServices {
#Bean
public FooRespository fooRepositiory() {
return Mockito.mock(FooRespository.class);
}
#Bean
public FooService fooService() {
return Mockito.mock(FooService.class);
}
//even though we are "only" testing FooController, we still need to mock BarController's dependencies, because BarController is loaded by the web app context.
#Bean
public BarService barService() {
return Mockito.mock(FooService.class);
}
//many more "mocks"
}
I do not really want to use standaloneSetup() (want to use the production configuration, eg conversion services, error handlers, etc)
is this just the price I have to pay for writing controller tests so far down the line?
Seems there should be something like mock every class annotated with #Service or mock every interface that extends JpaRepository
An MVC Controller is implemented normally like a glue code that integrates the Model with the View. For example, when invoking an EJB from the Controller and then updating the View model.
So, a Controller test may have sence when indeed you mock all your dependencies and verify that this integration or "glue code" is working as expected. In general, if an integration test implies too many components, maybe a modularization of the whole sut may be necessary for the system to be actually testable.
Anyway, if you find integration test laborious, maybe you can try to get the most coverage for each standalone component and let functional tests get the Controller coverage.

Categories