我熟悉复合注释。然而,即使经过一些研究,它们似乎还不足以满足我的特定需求。
我想为 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 中实现类似的目标?
您可以使用
ContextCustomizer
来实现此目的。
此方法允许您在运行测试之前修改应用程序上下文,从而提供对上下文配置的细粒度控制,包括添加或覆盖 bean、属性等。
要通过
ContextCustomizer
实现您的目标,请按照以下步骤操作:
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();
}
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.
}
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;
}
}
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。