Spring 引导集成测试通过注释加倍?

Spring Boot Integration test doubles by annotations?

我正在准备一个 Spring 引导程序(用于测试),我希望最终用户可以使用一些测试替身轻松更改生产代码。这些应该是模块化和独立的,即:

我目前的解决方案基于 AutoConfiguration 类 with @Profile and @Primary @Bean:

@Configuration
@Profile("testClock")
public class FixedClockConfiguration {

    @Value("${neostarter.test.clock:2010-01-10T10:00:00Z}")
    private String fixedClock;

    @Bean
    @Primary
    Clock clock() {
        return Clock.fixed(Instant.parse(fixedClock), TimeZone.getDefault().toZoneId());
    }
}

然后要使用它,我需要在我的 IT 中设置 @ActiveProfiles 并提供时钟值,如果我不喜欢默认值 @TestPropertySource:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
@WebIntegrationTest
@ActiveProfiles("testClock")
@TestPropertySource(properties = "neostarter.test.clock=2015-05-05T10:00:00Z")
public class IntegrationTest {

相同的模式将适用于我 IT 中的所有测试替身,因此我需要添加更多活动配置文件(可能还有一些测试属性):

@ActiveProfiles({"testClock", "testAuth"})

有什么方法可以将其转化为基于注释的解决方案吗?我想实现的是一组简单的注释:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
@WebIntegrationTest
@TestClock("2015-05-05T10:00:00Z")
@TestAuth(roles = {"admin", "user"})
public class IntegrationTest {

会做同样的事情(或者最终结果会是一样的),其中 @TestClock@TestAuth 是完全独立和可选的。

使用 Spring Boot 1.3 并没有特别简单的方法来做到这一点。可能您最好的选择是将注释从测试 class 转移到配置,例如:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
public class ExampleTests {

    @Autowired
    private Clock clock;

    @Test
    public void test() throws Exception {
        System.out.println(this.clock);
    }

    @Configuration
    @Import(SampleSimpleApplication.class)
    @TestClock("2015-05-05T10:00:00Z")
    static class Config {
    }

}

然后您可以 @TestClock 导入注册商:

@Retention(RetentionPolicy.RUNTIME)
@Import(TestClockRegistrar.class)
public @interface TestClock {

    String value();

}

注册商读取注释并创建 bean:

public class TestClockRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        String pattern = (String) importingClassMetadata
                .getAnnotationAttributes(TestClock.class.getName()).get("value");
        BeanDefinition beanDefinition = new RootBeanDefinition(Clock.class);
        beanDefinition.setFactoryMethodName("fixed");
        Instant instant = Instant.parse(pattern);
        ZoneId zone = TimeZone.getDefault().toZoneId();
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, instant);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, zone);
        registry.registerBeanDefinition("clock", beanDefinition);
    }

}

当然,我不确定这比简单地删除注释要好得多:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration
public class ExampleTests {

    @Autowired
    private Clock clock;

    @Test
    public void test() throws Exception {
        System.out.println(this.clock);
    }

    @Configuration
    @Import(SampleSimpleApplication.class)
    static class Config {

        @Bean
        @Primary
        public Clock clock() {
            Clock.fixed(...)
        }

    }

}

我们正在积极考虑为 Spring Boot 1.4 提供更好的模拟支持。您可以关注 https://github.com/spring-projects/spring-boot/issues/5042 以获取进度。