【问题标题】:Spring boot: Inject beans from tests into web environmentSpring boot:将测试中的bean注入Web环境
【发布时间】:2020-05-11 23:29:01
【问题描述】:

我正在为我的示例应用程序构建一些集成测试,并且想知道是否可以在我的测试本身中创建一些测试数据,然后将其注入到正在运行的服务器中。我不想模拟我的数据,因为我希望我的测试贯穿整个堆栈。

我了解 Spring Boot 文档说服务器和测试在 2 个单独的线程中运行,但是否可以通过相同的上下文?

我目前的代码:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ArtistResourceTests {
    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ArtistRepository artistRepository;

    @Test
    @Transactional
    public void listReturnsArtists() {
        Artist artist = new DataFactory().getArtist();
        this.artistRepository.save(artist);

        ParameterizedTypeReference<List<Artist>> artistTypeDefinition = new ParameterizedTypeReference<List<Artist>>() {};
        ResponseEntity<List<Artist>> response = this.restTemplate.exchange("/artists", HttpMethod.GET, null, artistTypeDefinition);

        assertEquals(1, response.getBody().size());
    }
}

但这会返回 0 个结果而不是 1 个结果。

【问题讨论】:

    标签: java spring spring-boot junit junit5


    【解决方案1】:

    我认为您不会与某些远程运行的服务器进行交互。

    SpringBootTest 注解在测试中本地启动整个微服务。 否则,如果您的测试只是对远程服务器的一系列调用,那么您实际上并不需要 @SpringBootTest(并且根本不需要 spring boot :))。

    所以你在测试中就有了一个应用程序上下文。 现在您要问的是如何预填充数据。这太宽泛了,因为您没有指定数据的确切存储位置以及涉及哪些数据持久层(RDBMS、Elasticsearch、Mongo...)?

    一种可能的通用方式是使用Test Execution Listener,它可以有一个方法beforeTestMethod

    应用程序上下文已启动,因此您可以真正以自定义方式准备数据并将其保存到您选择的数据库中(通过将 DAO 注入侦听器或其他方式)。

    如果您使用 Flyway,另一个有趣的方法是在 src/test/resources/data 文件夹中提供迁移,以便 flyway 在测试期间自动执行迁移。

    更新

    评论指出,使用了 H2 DB,在这种情况下,假设数据源配置正确并且确实提供了到 H2 的连接,最简单的方法是运行带有数据插入的 SQL 脚本:

    @Sql(scripts = {"/scripts/populateData1.sql", ..., "/scripts/populate_data_N.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    public void myTest() {
    ...
    }
    
    

    现在,如果您必须使用

    this.artistRepository.save(artist);
    

    那么 spring 并不关心线程。只要“数据”是 bean(或资源),它就可以注入任何数据,因为您正在处理对象(艺术家),所以它必须是 bean。

    因此,使用 bean Artist 创建 TestConfiguration,确保它与 test 在同一个包中(以便 spring boot 测试扫描过程将加载配置)并像往常一样使用 @Autowired 将其注入到测试中:

    @TestConfiguration
    public class ArtistsConfiguration {
    
       @Bean 
       public Artist artistForTests() {
           return new Artist(...);
       }
    }
    
    
    @Import(ArtistsConfiguration.class)
    @SpringBootTest
    public class MyTest {
    
       @Autowired
       private Artist artist;
    
       ....
    }
    

    【讨论】:

    • 据我了解,它不是一些“远程运行的服务器”。 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 只是在一个单独的线程中启动您的应用程序并侦听“随机端口”,我的问题是“如何在该线程中从我的存储库的测试(来自 datafactory 的艺术家)中注入我的测试数据?” .我目前正在使用 H2 数据库。
    • 请查看更新,希望它能解决问题
    • 马克,感谢您的回答,但存储库已经是一个 bean。它是可用的。问题是内存数据库中目前有 2 个。测试之一和服务器本身之一。更多信息:docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/…
    • 显然是@Transactional 引起了问题。对于我的例子来说就足够了,但是当使用 postgres 或其他东西时,这会导致问题(因为数据是持久化的)
    • 但问题依然存在,能否以简洁的方式回滚单独的线程?
    【解决方案2】:

    你可以像这样使用@ContextConfiguration:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ActiveProfiles("test")
    @ContextConfiguration(locations = {"/testApplicationContext.xml"})
    public class TestGetPerson {
    
        @Autowired
        private PersonService personService;
    ...
    

    然后在 testApplicationContext 中指定 Spring 应该扫描的包:

    <context:component-scan base-package="nl.example.server.service"/>
    <context:component-scan base-package="nl.example.server.test.db"/>
    

    可能有一个注解可以达到同样的目的。 我所做的是让 Spring 扫描大多数包与实时应用程序中的相同,除了数据库组件:我使用内存 H2 数据库进行测试。 Profiles 注释告诉 Spring 在测试中要使用哪些类。 此外,您可以将某些类配置(在扫描的包中使用 @Configuration)为 Mockito 模拟:

    @Configuration
    @Profile("test")
    public class CustomerManagerConfig {
    @Bean("customerManager")
    
        public CustomerManager customerManager() {
            return Mockito.mock(CustomerManager.class);
        }
    }
    

    这不会在单独的服务器上运行您的测试数据,但它会在尽可能接近您的应用程序环境的环境中运行测试。

    关于您的问题,您可以创建自己的模拟,而不是使用 Mockito 模拟,将您的测试数据注入您喜欢的任何组件中。

    【讨论】:

    • 嗨,Christine,感谢您的回复,但并没有真正回答我的问题。我正在使用这个设置:docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/…your test is @Transactional, it rolls back the transaction at the end of each test method. Using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case
    猜你喜欢
    • 2022-01-19
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-14
    • 2012-04-06
    • 2017-10-23
    相关资源
    最近更新 更多