Issue
I'm working on a Spring-Boot web application. The usual way of writing integration tests is:
@Test
@Transactional
@Rollback(true)
public void myTest() {
// ...
}
This works nicely as long as only one thread does the work. @Rollback
cannot work if there are multiple threads.
However, when testing a @RestController
class using the Spring REST templates, there are always multiple threads (by design):
- the test thread which acts as the client and runs the REST template
- the server thread which receives and processes the request
So you can't use @Rollback
in a REST test. The question is: what do you use instead to make tests repeatable and have them play nicely in a test suite?
@DirtiesContext
works, but is a bad option because restarting the Spring application context after each REST test method makes the suite really slow to execute; each test takes a couple of milliseconds to run, but restarting the context takes several seconds.
Solution
First of all, testing a controller using a Spring context is no unit test. You should consider writing a unit test for the controller by using mocks for the dependencies and creating a standalone mock MVC:
public class MyControllerTest {
@InjectMocks
private MyController tested;
// add @Mock annotated members for all dependencies used by the controller here
private MockMvc mvc;
// add your tests here using mvc.perform()
@Test
public void getHealthReturnsStatusAsJson() throws Exception {
mvc.perform(get("/health"))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status", is("OK")));
}
@Before
public void createControllerWithMocks() {
MockitoAnnotations.initMocks(this);
MockMvcBuilders.standaloneSetup(controller).build()
}
}
This even works if you use an external @ControllerAdvice
for error handling etc by simply calling setControllerAdvice()
on the MVC builder.
Such a test has no problems running in parallel and is much faster by no need to setup a Spring context at all.
The partial integration test you described is also useful to make sure the right wiring is used and all tested units work together as expected. But I would more go for a more general integration test including multiple/all endpoints checking if they work in general (not checking the edge cases) and mocking only services reaching out to external (like internal REST clients, replacing the database by one in memory, ...). With this setup you start with a fresh database and maybe will not even need to rollback any transaction. This is of course most comfortable using a database migration framework like Liquibase which would setup your in memory db on the fly.
Answered By - Arne Burmeister
Answer Checked By - Clifford M. (JavaFixing Volunteer)