在测试 class 中使用 @PostConstruct 会导致它被多次调用

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

我正在编写集成测试来测试我的端点,并且需要在构造后立即在数据库中设置一个用户,因此 Spring 安全测试注释 @WithUserDetails 有一个用户可以从数据库中收集。

我的 class 设置是这样的:

@RunWith(value = SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "email@address.com")
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
    }

}

但是 每个 注释 @Test 都会调用 @PostConstruct 方法,即使我们没有再次实例化主 class。

因为我们使用 Spring 安全测试 (@WithUserDetails),所以我们需要在使用 JUnit 注释 @Before 之前将用户持久保存到数据库中。我们也不能使用 @BeforeClass,因为我们依赖 @Autowired 服务:aService

我找到的一个解决方案是使用一个变量来确定我们是否已经设置了数据(见下文),但这感觉很脏,并且会有更好的方法。

@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;
    }
}

TLDR:暂时保持原样。如果稍后在多个测试中重复布尔标志 classes 创建你自己的 TestExecutionListener.

在 JUnit 中,测试 class 构造函数在每个测试方法执行时被调用。
因此,为每个测试方法调用 @PostConstruct 是有道理的。
根据 JUnit 和 Spring 功能,您的解决方法还不错。特别是因为你在基础测试中这样做 class。

作为一种不那么肮脏的方法,您可以使用 @TestExecutionListeners 注释您的测试 class 并提供自定义 TestExecutionListener 但它在这里似乎有点过分,因为您只使用过一次。
在您没有 have/want 基数 class 并且您想在多个 class 中添加布尔标志的情况下,使用自定义 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
    }
}

测试 class 已更新:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@WithUserDetails(value = "email@address.com")
@TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, 
                        value=MyMockUserTestExecutionListener.class) 
public abstract class IntegrationTests {
 ...
}

请注意,如果您想合并来自 Spring 启动测试 class 的 TestExecutionListenerTestExecutionListener 中定义的 TestExecutionListener =14=] 当前 class.
默认值为 MergeMode.REPLACE_DEFAULTS.