We have a quite big suite of tests (integration/unit/cucumber....).
Now we want to add an additional integration test using the following code:
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ActiveProfiles("integrationTest")
#AutoConfigureMockMvc
class MyIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MyRepository myRepository;
#Test
void myTest() throws Exception {
final var argument = ArgumentCaptor.forClass(MyEntity.class);
mockMvc.perform(
post(ENDPOINT)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content("{}")
.secure(true))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
verify(myRepository).saveAndFlush(argument.capture());
final var payload = argument.getValue().getRequest();
assertThat(payload).doesNotContainAnyWhitespaces();
}
This is MyRepository definition
#Repository
public interface RiskScoreRepository extends JpaRepository<..> {}
If I run this test only there are no issues. As soon as I run mvn verify I get the following error:
Wanted but not invoked:
myRepository bean.saveAndFlush(
<Capturing argument>
);
-> at MyIntegrationTest.registrationWorksThroughAllLayers(MyIntegrationTest.java:XX)
Actually, there were zero interactions with this mock.
I assume that this is because the Application context is already loaded by a previous integration test.
How can I solve this problem?
EDIT
I tried to disable all the others integrations tests and now MyIntegrationTest is passing even with mvn verify
How can solve this?
Thanks
Related
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?
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.
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
I am using MockitoJUnit library to test my controller. there are some variables which are declared as:
#Value("${mine.port}")
private Integer port;
when I run my test, it could not find the value mine.port in the resource folder as properties file. My controller is no problem to run, but the port number is null when I run my contoller junit test.
I used below annotation on my class:
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(classes = myControl.class)
#WebAppConfiguration
#AutoConfigureMockMvc
I used below method in my junit test
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(myController)
.build();
}
#Test
public void getName() throws Exception {
MockHttpServletRequestBuilder request =
MockMvcRequestBuilders.get("/service")
.contentType(MediaType.APPLICATION_JSON_VALUE);
MvcResult result = mockMvc.perform(request)
.andExpect(status().isOk()).andReturn(); }
I am confused, does anyone know if I am missing some settings? thx!
No one of the specified annotations will start a Spring container :
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(classes = myControl.class)
#WebAppConfiguration
#AutoConfigureMockMvc
To write a test slice focused on the controller don't use a Mockito runner : #RunWith(MockitoJUnitRunner.class) otherwise you could not start a Spring container. MockitoJUnitRunner is for unit tests, not integration tests.
To know when and how to write unit and integration tests with Spring Boot you could read this question.
Instead of, use the #WebMvcTest annotation with a Spring runner such as :
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#WebMvcTest
public class WebLayerTest {
...
}
You can rely on this Spring guide to have more details.
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).