本文介绍了在测试类中使用@PostConstruct导致多次调用它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写集成测试以测试我的端点,并且需要在构造后立即在数据库中设置用户,以便Spring Security Test批注@WithUserDetails可以从数据库中收集用户.

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
    }

}

尽管我们没有再次实例化主类,但是@PostConstruct方法却被每个带注释的@Test调用.

However the @PostConstruct method is called for every annotated @Test, even though we are not instantiating the main class again.

因为我们使用Spring Security Test(@WithUserDetails),所以我们需要用户持久化到数据库,然后才能使用JUnit批注@Before.我们也不能使用@BeforeClass,因为我们依赖@Autowired服务:aService.

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

推荐答案

TLDR:暂时保持当前状态.如果以后在多个测试类中重复使用boolean标志,则创建您自己的TestExecutionListener.

TLDR : Keep your way for the moment. If later the boolean flag is repeated in multiple test classes create your own TestExecutionListener.

在JUnit中,每个执行的测试方法都会调用测试类构造函数.
因此,为每个测试方法调用@PostConstruct是很有意义的.
根据JUnit和Spring的功能,您的解决方法还不错.特别是因为您是在基本测试类中进行的.

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.

以一种不太肮脏的方式,您可以用@TestExecutionListeners注释测试类并提供自定义的TestExecutionListener,但是在您一次使用它的时候,这似乎有点过头了.
在您没有/想要基类并且想要在多个类中添加布尔标志的情况下,使用自定义TestExecutionListener可能很有意义.这是一个例子.

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.

自定义TestExecutionListener:

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

测试类已更新:

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

请注意,如果要将来自Spring Boot测试类的TestExecutionListener与当前类的@TestExecutionListeners中定义的TestExecutionListener合并,则MergeMode.MERGE_WITH_DEFAULTS很重要.
默认值为MergeMode.REPLACE_DEFAULTS.

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.

这篇关于在测试类中使用@PostConstruct导致多次调用它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

06-12 22:51