matthew.kempson
matthew.kempson

Reputation: 1054

Using @PostConstruct in a test class causes it to be called more than once

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

Answers (1)

davidxxx
davidxxx

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 TestExecutionListeners coming from the Spring Boot test class with TestExecutionListeners defined in the @TestExecutionListeners of the current class.
The default value is MergeMode.REPLACE_DEFAULTS.

Upvotes: 3

Related Questions