在Spring配置中实现带有可读属性的自定义注解(测试)

问题描述 投票:0回答:1

我熟悉复合注释。然而,即使经过一些研究,它们似乎还不足以满足我的特定需求。

一般情况

我想为 testing 创建一个注释,将其与一些属性放在一个类上,可以自定义测试上下文。

public @interface MyTestingAnnotation {

    String myTestingAttribute()

}

myTestingAttribute
的值应由我的自定义之一读取(TBD)
@TestConfiguration
s

实际例子

在我的应用程序中,我必须模拟时钟,以便测试可以模拟特定时间点运行。例如。测试结果不依赖于硬件时钟。为此,我定义了一个

java.time.Clock
bean。

目前,我只有一个注释来启用模拟时钟,但指定其时间取决于

@TestPropertySource

/**
 * Provides mocking configuration for the {@link Clock} bean
 * <p></p>
 * Injected in all relevant Spring beans that require time access, can be set to a fixed time in order to provide repeatable
 * tests
 * In general, time zone is assumed to be UTC in all cases.
 * <p></p>
 * This bean reads the property <pre>clock.fixedInstant</pre> to determine the current time that is being mocked to all beans
 * By default, it is a known point in time: <pre>4 November 2023 at 15:45:32 UTC</pre>
 * Individual test can override the fixed time by using
 * <pre>@TestPropertySource(properties = "clock.fixed-instant:[... ISO 8601 ...]")</pre>
 */
@TestConfiguration
public class MockClockConfiguration {

    @Value("${clock.fixed-instant:2023-11-04T15:45:32.000000Z}")
    private OffsetDateTime clockFixedInstant;

    private static final ThreadLocal<OffsetDateTime> fixedInstantLocal = new ThreadLocal<>();

    /**
     * Convenience method for tests to retrieve the fixed time
     * @return
     */
    public static OffsetDateTime getFixedInstant(){
        return fixedInstantLocal.get();
    }

    @Primary
    @Bean
    public Clock fixedClock() {
        fixedInstantLocal.set(clockFixedInstant);
        return Clock.fixed(Instant.from(clockFixedInstant), clockFixedInstant.getOffset());
    }

}

相反,我想注释像

@MockClock(at = "2024-02-15T12:35:00+04:00")
这样的测试,而不一定使用属性源语法。

我知道如何在自定义注释中使用

@AliasFor
,但目前我只能在元注释中使用
@Import(MockClockConfiguration.class)

如何在 Spring 中实现类似的目标?

spring spring-test spring-annotations
1个回答
4
投票

您可以使用

ContextCustomizer
来实现此目的。

此方法允许您在运行测试之前修改应用程序上下文,从而提供对上下文配置的细粒度控制,包括添加或覆盖 bean、属性等。

要通过

ContextCustomizer
实现您的目标,请按照以下步骤操作:

  1. 创建自定义注释
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MockClock {
    String at();
}
  1. 实施
    ContextCustomizer

创建

ContextCustomizer
,它将根据
@MockClock
注释修改应用程序上下文的环境或bean定义。

import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;

import java.time.OffsetDateTime;

public class MockClockContextCustomizer implements ContextCustomizer {

    private final OffsetDateTime mockTime;

    public MockClockContextCustomizer(OffsetDateTime mockTime) {
        this.mockTime = mockTime;
    }

    @Override
    public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
        TestPropertyValues.of(
                        "clock.fixed-instant=" + mockTime.toString())
                .applyTo(context);
    }

    // MockClockContextCustomizer must implement equals() and hashCode(). See the Javadoc for ContextCustomizer for details.
}
  1. 实施
    ContextCustomizerFactory

创建一个

ContextCustomizerFactory
,在测试类上查找
@MockClock
注释,并根据注释的属性生成
ContextCustomizer

import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;

import java.time.OffsetDateTime;
import java.util.List;

public class MockClockContextCustomizerFactory implements ContextCustomizerFactory {
    @Override
    public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
        MockClock mockClock = testClass.getAnnotation(MockClock.class);
        if (mockClock != null) {
            return new MockClockContextCustomizer(OffsetDateTime.parse(mockClock.at()));
        }
        return null;
    }
}
  1. 注册
    ContextCustomizerFactory

Spring 不会自动发现 ContextCustomizerFactory 实现。要注册您的自定义工厂,您需要在项目的资源目录中创建一个名为

META-INF/spring.factories
的文件(如果它尚不存在)并添加以下行:

org.springframework.test.context.ContextCustomizerFactory=\
your.package.MockClockContextCustomizerFactory

your.package
替换为您的
MockClockContextCustomizerFactory
所在的实际包名称。

作为替代方案,从 Spring Framework 6.1 开始,您可以通过

ContextCustomizerFactory
在本地注册
@ContextCustomizerFactories

如何运作

  • 当执行带有
    @MockClock
    注解的测试类时,Spring Test 会查找
    ContextCustomizerFactory
    中指定的
    spring.factories
    实现。
  • 您的
    MockClockContextCustomizerFactory
    检查是否存在
    @MockClock
    注释,如果找到,则使用指定的固定时刻创建
    MockClockContextCustomizer
    的实例。
  • 然后,
    MockClockContextCustomizer
    会在测试运行之前自定义应用程序上下文,将所需的属性添加到环境中,您的
    MockClockConfiguration
    可以使用它来创建模拟的 Clock bean。
© www.soinside.com 2019 - 2024. All rights reserved.