Reputation: 1054
I am writing integration tests to test my endpoints and need to setup a User in the database right after construct so the Spring Security Test annotation @WithUserDetails
has a user to collect from the database.
My class setup is like this:
@RunWith(value = SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "[email protected]")
public abstract class IntegrationTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private Service aService;
@PostConstruct
private void postConstruct() throws UserCreationException {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
}
@Test
public void testA() {
// Some test
}
@Test
public void testB() {
// Some test
}
@Test
public void testC() {
// Some test
}
}
However the @PostConstruct
method is called for every annotated @Test
, even though we are not instantiating the main class again.
Because we use Spring Security Test (@WithUserDetails
) we need the user persisted to the database BEFORE we can use the JUnit annotation @Before
. We cannot use @BeforeClass
either because we rely on the @Autowired
service: aService
.
A solution I found would be to use a variable to determine if we have already setup the data (see below) but this feels dirty and that there would be a better way.
@PostConstruct
private void postConstruct() throws UserCreationException {
if (!setupData) {
// Setup and save user data to the db using autowired service "aService"
RestAssuredMockMvc.mockMvc(mockMvc);
setupData = true;
}
}
Upvotes: 3
Views: 3077
Reputation: 131476
TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener
.
In JUnit, the test class constructor is invoked at each test method executed.
So it makes sense that @PostConstruct
be invoked for each test method.
According to JUnit and Spring functioning, your workaround is not bad. Specifically because you do it in the base test class.
As less dirty way, you could annotate your test class with @TestExecutionListeners
and provide a custom TestExecutionListener
but it seem overkill here as you use it once.
In a context where you don't have/want the base class and you want to add your boolean flag in multiple classes, using a custom TestExecutionListener
can make sense.
Here is an example.
Custom TestExecutionListener :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class MyMockUserTestExecutionListener extends AbstractTestExecutionListener{
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
MyService myService = testContext.getApplicationContext().getBean(MyService.class);
// ... do my init
}
}
Test class updated :
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "[email protected]")
@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS,
value=MyMockUserTestExecutionListener.class)
public abstract class IntegrationTests {
...
}
Note that MergeMode.MERGE_WITH_DEFAULTS
matters if you want to merge TestExecutionListener
s coming from the Spring Boot test class with TestExecutionListener
s defined in the @TestExecutionListeners
of the current class.
The default value is MergeMode.REPLACE_DEFAULTS
.
Upvotes: 3