文档章节

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

jarchan
 jarchan
发布于 2017/09/05 09:00
字数 1069
阅读 5.8K
收藏 102

#程序员薪资揭榜#你做程序员几年了?月薪多少?发量还在么?>>>

码云地址

在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][javadoc-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。

参考文档

© 著作权归作者所有

jarchan

jarchan

粉丝 59
博文 13
码字总数 8798
作品 2
崇明
技术主管
私信 提问
加载中

评论(7)

OSC_ZReLgh
OSC_ZReLgh
@jarchan 这么好用
OSC_ZReLgh
OSC_ZReLgh
不错,挺好
济天下68
济天下68

引用来自“名字太难取了”的评论

引用来自“济天下68”的评论

为什么手机app看代码片,显示不全?
你是什么手机 我在手机上是可以左右滑动的 没有问题

安卓的索尼
名字太难取了
名字太难取了

引用来自“济天下68”的评论

为什么手机app看代码片,显示不全?
你是什么手机 我在手机上是可以左右滑动的 没有问题
jarchan
jarchan 博主

引用来自“济天下68”的评论

为什么手机app看代码片,显示不全?
请找 @红薯 反应bug
济天下68
济天下68
为什么手机app看代码片,显示不全?
hantsy
hantsy
Great post
Spring Boot 和 testNG 和 eclipse背景色

通过之前的博文,我们已经验证,Spring环境下完成access数据访问没有问题。下面我们直接在Spring环境下部署我们的升级项目。 1.导入Spring Boot Spring Boot是Spring的子项目,用来解决项目配...

pandamonica
2017/09/19
0
0
Java - Test - TestNG: Idea 添加 TestNG 依赖

概述 Idea 开发的 maven 添加 testng 依赖 2. 背景 最近复习 TestNG 尝试搭建环境 发现教材和网上很多的教程, 都是 eclipse 的 我用的是 idea 这个貌似很多地方都没找到, 或者说, 讲得不清楚...

osc_ywuazj5t
2019/11/08
2
0
eclipse上cxf与testNG的冲突

我打算在eclipse上的一个maven项目的某一个类发布,成为web service,打算使用cxf进行发布。 最初这个测试用的maven项目是这样的: 也就是说,在导入cxf之前,这个maven项目的功能是可以正常...

pandamonica
2018/09/07
0
0
使用testNGListenter来自定义日志

背景 用testNG写用例的时候,只是打印了请求的日志,没有打印这个用例的开始和结束的标识,想加上这个标识这样更好的排查问题 这种日志是加在用例开始执行和结束,相当于spring中的AOP功能,...

osc_8dm0sbbd
2018/01/10
2
0
JBoss Developer Studio 4.1 发布

JBoss Developer Studio 4.1 发布了,这是一个更新版本,主要包括: 基于 Eclipse 3.6.2 (Helios SR2) 修复了很多主要部件的bug 完全支持 BPEL 编辑器 同时 4.1 包含经过认证的第三方附加包:...

红薯
2011/08/19
947
0

没有更多内容

加载失败,请刷新页面

加载更多

Python笔记:批量提取PDF表格数据(每页都含有相同表头的)

现在有一份PDF数据,里面有两页,每页都有相同的数据结构但数据内容不同的数据表(每页都有相同的表头),现在需要使用Python将它批量提取出来。 我们需要使用pdfplumber模块(因为有墙的原因...

tengyulong
27分钟前
16
0
一个 TCP 连接可以发多少个 HTTP 请求?

曾经有这么一道面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什...

onedotdot
36分钟前
21
0
javafx+springboot+mybatis异常

1:实验目录结构: 2:主要代码: @SpringBootApplication@MapperScan("com.joe.naautotest.Mapper")public class NaautotestApplication extends AbstractJavaFxApplicationSupport ......

whoisliang
53分钟前
24
0
Spring Boot接口幂等性封装

Spring Boot接口幂等性封装 封装思路 接口幂等性后端的处理方式,就是通过redis来验证表单提交时申请的token有效性。因此,我们可以利用Spring Boot的自动装配特性,针对此功能封装一个可用的...

旺仔没馒头
今天
20
0
使用“ for”循环遍历字典 - Iterating over dictionaries using 'for' loops

问题: I am a bit puzzled by the following code: 以下代码使我有些困惑: d = {'x': 1, 'y': 2, 'z': 3} for key in d: print key, 'corresponds to', d[key] What I don't unders......

技术盛宴
今天
20
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部