专栏首页颇忒脱的技术博客Spring、Spring Boot和TestNG测试指南 - 测试@Configuration

Spring、Spring Boot和TestNG测试指南 - 测试@Configuration

Github地址

在Spring引入Java Config机制之后,我们会越来越多的使用@Configuration来注册Bean,并且Spring Boot更广泛地使用了这一机制,其提供的大量Auto Configuration大大简化了配置工作。那么问题来了,如何确保@Configuration和Auto Configuration按照预期运行呢,是否正确地注册了Bean呢?本章举例测试@Configuration和Auto Configuration的方法(因为Auto Configuration也是@Configuration,所以测试方法是一样的)。

例子1:测试@Configuration

我们先写一个简单的@Configuration:

@Configuration
public class FooConfiguration {

  @Bean
  public Foo foo() {
    return new Foo();
  }

}

然后看FooConfiguration是否能够正确地注册Bean:

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test
  public void testFooCreation() {
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

注意上面代码中关于Context的代码:

  1. 首先,我们构造一个Context
  2. 然后,注册FooConfiguration
  3. 然后,refresh Context
  4. 最后,在测试方法结尾close Context

如果你看Spring Boot中关于@Configuration测试的源代码会发现和上面的代码有点不一样:

public class DataSourceAutoConfigurationTests {

    private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

    @Before
    public void init() {
        EmbeddedDatabaseConnection.override = null;
        EnvironmentTestUtils.addEnvironment(this.context,
                "spring.datasource.initialize:false",
                "spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt());
    }

    @After
    public void restore() {
        EmbeddedDatabaseConnection.override = null;
        this.context.close();
    }

这是因为Spring和Spring Boot都是用JUnit做测试的,而JUnit的特性是每次执行测试方法前,都会new一个测试类实例,而TestNG是在共享同一个测试类实例的。

例子2:测试@Conditional

Spring Framework提供了一种可以条件控制@Configuration的机制,即只在满足某条件的情况下才会导入@Configuration,这就是@Conditional

下面我们来对@Conditional做一些测试,首先我们自定义一个Condition FooConfiguration

@Configuration
public class FooConfiguration {

  @Bean
  @Conditional(FooCondition.class)
  public Foo foo() {
    return new Foo();
  }

  public static class FooCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      if (context.getEnvironment() != null) {
        Boolean property = context.getEnvironment().getProperty("foo.create", Boolean.class);
        return Boolean.TRUE.equals(property);
      }
      return false;
    }

  }
}

该Condition判断Environment中是否有foo.create=true

如果我们要测试这个Condition,那么就必须往Environment里添加相关property才可以,在这里我们测试了三种情况:

  1. 没有配置foo.create=true
  2. 配置foo.create=true
  3. 配置foo.create=false

FooConfigurationTest

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyNull() {
    context.register(FooConfiguration.class);
    context.refresh();
    context.getBean(Foo.class);
  }

  @Test
  public void testFooCreatePropertyTrue() {
    context.getEnvironment().getPropertySources().addLast(
        new MapPropertySource("test", Collections.singletonMap("foo.create", "true"))
    );
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyFalse() {
    context.getEnvironment().getPropertySources().addLast(
        new MapPropertySource("test", Collections.singletonMap("foo.create", "false"))
    );
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

注意我们用以下方法来给Environment添加property:

context.getEnvironment().getPropertySources().addLast(
  new MapPropertySource("test", Collections.singletonMap("foo.create", "true"))
);

所以针对@Conditional和其对应的Condition的测试的根本就是给它不一样的条件,判断其行为是否正确,在这个例子里我们的Condition比较简单,只是判断是否存在某个property,如果复杂Condition的话,测试思路也是一样的。

例子3:测试@ConditionalOnProperty

Spring framework只提供了@Conditional,Spring boot对这个机制做了扩展,提供了更为丰富的@ConditionalOn*,这里我们以@ConditionalOnProperty举例说明。

先看FooConfiguration

@Configuration
public class FooConfiguration {

  @Bean
  @ConditionalOnProperty(prefix = "foo", name = "create", havingValue = "true")
  public Foo foo() {
    return new Foo();
  }

}

FooConfigurationTest

public class FooConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyNull() {
    context.register(FooConfiguration.class);
    context.refresh();
    context.getBean(Foo.class);
  }

  @Test
  public void testFooCreatePropertyTrue() {
    EnvironmentTestUtils.addEnvironment(context, "foo.create=true");
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

  @Test(expectedExceptions = NoSuchBeanDefinitionException.class)
  public void testFooCreatePropertyFalse() {
    EnvironmentTestUtils.addEnvironment(context, "foo.create=false");
    context.register(FooConfiguration.class);
    context.refresh();
    assertNotNull(context.getBean(Foo.class));
  }

}

这段测试代码和例子2的逻辑差不多,只不过例子2里使用了我们自己写的Condition,这里使用了Spring Boot提供的@ConditionalOnProperty。

并且利用了Spring Boot提供的EnvironmentTestUtils简化了给Environment添加property的工作:

EnvironmentTestUtils.addEnvironment(context, "foo.create=false");

例子4:测试Configuration Properties

Spring Boot还提供了类型安全的Configuration Properties,下面举例如何对其进行测试。

BarConfiguration

@Configuration
@EnableConfigurationProperties(BarConfiguration.BarProperties.class)
public class BarConfiguration {

  @Autowired
  private BarProperties barProperties;

  @Bean
  public Bar bar() {
    return new Bar(barProperties.getName());
  }

  @ConfigurationProperties("bar")
  public static class BarProperties {

    private String name;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }
  }

}

BarConfigurationTest

public class BarConfigurationTest {

  private AnnotationConfigApplicationContext context;

  @BeforeMethod
  public void init() {
    context = new AnnotationConfigApplicationContext();
  }

  @AfterMethod(alwaysRun = true)
  public void reset() {
    context.close();
  }

  @Test
  public void testBarCreation() {
    EnvironmentTestUtils.addEnvironment(context, "bar.name=test");
    context.register(BarConfiguration.class, PropertyPlaceholderAutoConfiguration.class);
    context.refresh();
    assertEquals(context.getBean(Bar.class).getName(), "test");
  }

}

注意到因为我们使用了Configuration Properties机制,需要注册PropertyPlaceholderAutoConfiguration,否则在BarConfiguration里无法注入BarProperties。

参考文档

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 面向程序员的网络基本知识 - IP地址

    本系列文章旨在向程序员分享一些网络基本知识,让程序员具备基本的网络常识,以便与网络工程师沟通。本系列文章不会涉及如何组建网络、如何配置交换机/路由器等硬件相关的...

    颇忒脱
  • Spring、Spring Boot和TestNG测试指南 - @OverrideAutoConfiguration

    在Chapter 1: 基本用法 - 使用Spring Boot Testing工具里提到:

    颇忒脱
  • 记一次K8S VXLAN Overlay网络8472端口冲突问题的排查

    服务器:172.17.17.20-22 三个服务器 (深信服aCloud虚拟化平台)

    颇忒脱
  • Android自定义View实现数字密码锁

    最近项目上用到一个密码加锁功能,需要一个数字密码界面,就想着封装成一个View来方便管理和使用。

    砸漏
  • Spring Cloud 项目 SpringApplication Run 执行两次解密

    作用是在ConfigurableApplicationContext类型的ApplicationContext.refresh 操作 之前,允许我们对Confi...

    冷冷
  • EF基础知识小记七(拆分实体到多个表以及拆分表到多个实体)

    一、拆分实体到多个表 1、在日常开发中,会经常碰到一些老系统,当客户提出一些新的需求,这些需求需要在原来的表的基础上加一些字段,大多数人会选择通过给原表添加字段...

    郑小超.
  • 通过添加HTTP Header实现上下文数据在WCF的自动传递

    多年之前,我写了一篇通过WCF扩展实现上下文信息从客户端自动传递到服务端的文章,其实现机制很简单:将上下文信息存放到SOAP Header进行传递。那么对于非S...

    蒋金楠
  • Springboot自动装配整理

    再模仿Spring Cloud Feign源码解析 中的@EnableFeignClients代码写一个我们自己的标签

    算法之名
  • 用C++跟你聊聊“桥接模式”

    桥接模式,号称设计模式中最抽象的一个,不是吹出来的啊。且看我能不能讲清楚啊。 这时候就体现出小故事的重要性了,这也是我为什么每篇设计模式都要先讲个小故事,便于理...

    看、未来
  • Spring 基于注解(annotation)的配置之@Qualifier注解

    使用@Qualifier可以分别为同样类型的Bean分别注入不同的依赖值。看个例子:

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券