首页 > 解决方案 > 在测试类之外使用 Spring Boot/JUnit 5 连接到测试生命周期事件

问题描述

JUnit 5 具有@BeforeEach, @BeforeAll, @AfterAll,@AfterEach注释等功能,允许您在测试生命周期事件期间执行代码,例如清除测试之间共享的状态。

我想要一种为某些测试配置自动执行此操作的方法,而不必记住将这些生命周期挂钩放在每个测试中。

例如:

如果我有一个加载一些存根数据源的 Spring 测试配置:

interface DataProvider {
    String getData(String id);
}

还有一个依赖于此的类:

@Component
class ClassThatDependsOnDataProvider {
    private final DataProvider dataProvider;

    @Autowired
    ClassThatDependsOnDataProvider(DataProvider dataProvider) {
        this.dataProvider = dataProvider;
    }

    public String process(String id) {
        String data = dataProvider.get(id);
        return some_processing_of(data);
    }
}

通常这可能会遇到后端,但对于我的测试,我将使用内存解决方案将其换掉:

@TestComponent
class InMemoryDataProvider implements DataProvider {
    private Map<String, String> data = new HashMap<>();

    public void setData(String id, String data) {
        data.put(id, data);
    }

    public String getData(String id) {
        return data.get(id);
    }

    public void clear() {
        data.clear();
    }
}

在我的测试中,我可能想用它来存根后端的数据,例如:

@SpringBootTest
@Import({InMemoryDataProvider.class})
class ClassThatDependsOnDataProviderTests {
    @Autowired InMemoryDataProvider dataProvider;

    @Autowired ClassThatDependsOnDataProvider classThatDependsOnDataProvider;

    @Test
    public void should_correctly_process_data() {
        // given i have an id with a value
        dataProvider.setData("stub_id", "stub_value");

        // when i process my id
        var result = classThatDependsOnDataProvider.process("stub_id");

        // then i receive expected result
        assertThat(result).isWhatIExpect();
    }

    // more @Tests

    // DON'T WANT TO HAVE TO DO THIS IN EACH TEST CLASS
    @AfterEach
    public void tearDown() {
        dataProvider.clear();
    }
} 

我希望能够删除tearDown()这里用 注释的方法@AfterEach,并将该行为移动到测试组件/测试假或配置类中,这样,使用一组这样的提供程序,很容易维护一组的测试将用假版本替换它们,而无需为每个测试手动管理其生命周期。

是否有某种 Spring 功能、生命周期事件或 JUnit 5 功能允许这样做?

请原谅这个简单的例子,显然对于这么简单的测试来说太过分了,但这只是讨论的参考。假设@MockBean或使用模拟库不是一种选择。

标签: springspring-bootjunitjunit5

解决方案


您可以使用 JUnit5 扩展:https ://junit.org/junit5/docs/current/user-guide/#extensions 。

例如,您的扩展将类似于以下内容:

public class InMemoryDataProviderExtension implements AfterEachCallback {

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterEach(final ExtensionContext context) throws Exception {
        final ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);

        final InMemoryDataProvider inMemoryDataProvider = applicationContext.getBean(InMemoryDataProvider.class);
        inMemoryDataProvider.clear();

    }
}

注意:

  • SpringExtension.getApplicationContext(context);获取 spring 应用程序上下文,因此您可以检索上下文中定义的 bean
  • 该扩展实现了 AfterEachCallback 接口,但其他扩展点存在其他接口(即 BeforeEachCallback、BeforeAllCallback 等)

为了在您的测试中激活扩展:

@SpringBootTest
@ExtendWith(InMemoryDataProviderExtension.class)
public class ClassThatDependsOnDataProviderTests { ... }

推荐阅读