我正在尝试为我的程序中用于验证表单的简单 bean 编写单元测试。 bean 使用 @Component
进行注释,并有一个使用初始化的类变量
@Value("${this.property.value}") private String thisProperty;
我想为此类中的验证方法编写单元测试,但是,如果可能的话,我想在不使用属性文件的情况下这样做。我的理由是,如果我从属性文件中提取的值发生变化,我希望这不会影响我的测试用例。我的测试用例是测试验证值的代码,而不是值本身。
有没有办法在我的测试类中使用 Java 代码来初始化 Java 类并填充该类中的 Spring @Value 属性,然后使用它进行测试?
我确实发现这个 How To 似乎很接近,但仍然使用属性文件。我宁愿这一切都是Java代码。
如果可能的话,我会尝试在没有 Spring Context 的情况下编写这些测试。如果你在没有 spring 的测试中创建这个类,那么你可以完全控制它的字段。
要设置 @value
字段,您可以使用 Springs ReflectionTestUtils
- 它有一个方法 setField
来设置私有字段。
@见JavaDoc: ReflectionTestUtils.setField(java.lang.Object, java.lang.String, java.lang.Object)
从 Spring 4.1 开始,您可以通过在单元测试类级别上使用 org.springframework.test.context.TestPropertySource
注释在代码中设置属性值。您甚至可以使用这种方法将属性注入依赖的 bean 实例
例如
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = FooTest.Config.class)
@TestPropertySource(properties = {
"some.bar.value=testValue",
})
public class FooTest {
@Value("${some.bar.value}")
String bar;
@Test
public void testValueSetup() {
assertEquals("testValue", bar);
}
@Configuration
static class Config {
@Bean
public static PropertySourcesPlaceholderConfigurer propertiesResolver() {
return new PropertySourcesPlaceholderConfigurer();
}
}
}
注意:在 Spring 上下文中必须有 org.springframework.context.support.PropertySourcesPlaceholderConfigurer
的实例
编辑 24-08-2017: 如果您使用 SpringBoot 1.4.0 及更高版本,您可以使用 @SpringBootTest
和 @SpringBootConfiguration
注释初始化测试。更多信息here
在 SpringBoot 的情况下,我们有以下代码
@SpringBootTest
@SpringBootConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@TestPropertySource(properties = {
"some.bar.value=testValue",
})
public class FooTest {
@Value("${some.bar.value}")
String bar;
@Test
public void testValueSetup() {
assertEquals("testValue", bar);
}
}
不要通过反射滥用私有字段获取/设置
在这里的几个答案中使用反射是我们可以避免的。它在这里带来了很小的价值,同时也带来了多个缺点:
我们仅在运行时检测反射问题(例如:不再存在的字段)
我们想要封装,而不是一个不透明的类,它隐藏了应该可见的依赖项,并使类更不透明且更难测试。
它鼓励糟糕的设计。今天你声明了一个@Value String 字段。明天您可以在该类中声明 5 或 10 个,您甚至可能不会直接意识到您减少了该类的设计。使用更明显的方法来设置这些字段(例如构造函数),您在添加所有这些字段之前会三思而后行,您可能会将它们封装到另一个类中并使用@ConfigurationProperties。
使您的课程在统一和集成方面都可测试
为了能够为您的 Spring 组件类编写简单的单元测试(即没有运行的 spring 容器)和集成测试,您必须使这个类在有或没有 Spring 的情况下都可用。在不需要时在单元测试中运行容器是一种不好的做法,会减慢本地构建速度:您不希望这样。我添加了这个答案,因为这里没有答案似乎显示了这种区别,因此它们系统地依赖于正在运行的容器。
所以我认为你应该移动这个定义为类内部的属性:
@Component
public class Foo{
@Value("${property.value}") private String property;
//...
}
到将由 Spring 注入的构造函数参数中:
@Component
public class Foo{
private String property;
public Foo(@Value("${property.value}") String property){
this.property = property;
}
//...
}
单元测试示例
由于构造函数,您可以在没有 Spring 的情况下实例化 Foo
并为 property
注入任何值:
public class FooTest{
Foo foo = new Foo("dummyValue");
@Test
public void doThat(){
...
}
}
集成测试示例
由于 @SpringBootTest
的 properties
属性,您可以通过这种简单的方式在 Spring Boot 的上下文中注入属性:
@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{
@Autowired
Foo foo;
@Test
public void doThat(){
...
}
}
您可以使用 @TestPropertySource
作为替代,但它添加了一个额外的注释:
@SpringBootTest
@TestPropertySource(properties="property.value=dummyValue")
public class FooTest{ ...}
使用 Spring(没有 Spring Boot),它应该会稍微复杂一些,但是由于我很长一段时间都没有使用没有 Spring Boot 的 Spring,所以我不喜欢说一句愚蠢的话。
附带说明:如果要设置许多 @Value
字段,则将它们提取到使用 @ConfigurationProperties
注释的类中更为相关,因为我们不希望构造函数具有太多参数。
final
,即 private String final property
@Value
的字段,您建议创建一个带有 10 个参数的构造函数?
如果需要,您仍然可以在 Spring 上下文中运行测试并在 Spring 配置类中设置所需的属性。如果您使用 JUnit,请使用 SpringJUnit4ClassRunner 并为您的测试定义专用配置类,如下所示:
被测类:
@Component
public SomeClass {
@Autowired
private SomeDependency someDependency;
@Value("${someProperty}")
private String someProperty;
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SomeClassTestsConfig.class)
public class SomeClassTests {
@Autowired
private SomeClass someClass;
@Autowired
private SomeDependency someDependency;
@Before
public void setup() {
Mockito.reset(someDependency);
@Test
public void someTest() { ... }
}
这个测试的配置类:
@Configuration
public class SomeClassTestsConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer properties() throws Exception {
final PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
Properties properties = new Properties();
properties.setProperty("someProperty", "testValue");
pspc.setProperties(properties);
return pspc;
}
@Bean
public SomeClass getSomeClass() {
return new SomeClass();
}
@Bean
public SomeDependency getSomeDependency() {
// Mockito used here for mocking dependency
return Mockito.mock(SomeDependency.class);
}
}
话虽如此,我不会推荐这种方法,我只是在这里添加它以供参考。在我看来,更好的方法是使用 Mockito runner。在这种情况下,您根本不需要在 Spring 中运行测试,这更加清晰和简单。
这似乎有效,虽然仍然有点冗长(我想要更短的东西):
@BeforeClass
public static void beforeClass() {
System.setProperty("some.property", "<value>");
}
// Optionally:
@AfterClass
public static void afterClass() {
System.clearProperty("some.property");
}
@TestProperty
注释时。
@Value
。
在配置中添加 PropertyPlaceholderConfigurer 对我有用。
@Configuration
@ComponentScan
@EnableJpaRepositories
@EnableTransactionManagement
public class TestConfiguration {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
builder.setType(EmbeddedDatabaseType.DERBY);
return builder.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan(new String[] { "com.test.model" });
// Use hibernate
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
return entityManagerFactoryBean;
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.show_sql", "false");
properties.put("hibernate.dialect", "org.hibernate.dialect.DerbyDialect");
properties.put("hibernate.hbm2ddl.auto", "update");
return properties;
}
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
entityManagerFactory().getObject()
);
return transactionManager;
}
@Bean
PropertyPlaceholderConfigurer propConfig() {
PropertyPlaceholderConfigurer placeholderConfigurer = new PropertyPlaceholderConfigurer();
placeholderConfigurer.setLocation(new ClassPathResource("application_test.properties"));
return placeholderConfigurer;
}
}
在测试课上
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
public class DataServiceTest {
@Autowired
private DataService dataService;
@Autowired
private DataRepository dataRepository;
@Value("${Api.url}")
private String baseUrl;
@Test
public void testUpdateData() {
List<Data> datas = (List<Data>) dataRepository.findAll();
assertTrue(datas.isEmpty());
dataService.updateDatas();
datas = (List<Data>) dataRepository.findAll();
assertFalse(datas.isEmpty());
}
}
@ExtendWith(SpringExtension.class) // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
可能会有所帮助。关键是 ConfigDataApplicationContextInitializer 获取所有 props 数据
这是一个相当老的问题,我不确定当时它是否是一个选项,但这就是为什么我总是更喜欢构造函数而不是值的 DependencyInjection 的原因。
我可以想象你的班级可能是这样的:
class ExampleClass{
@Autowired
private Dog dog;
@Value("${this.property.value}")
private String thisProperty;
...other stuff...
}
您可以将其更改为:
class ExampleClass{
private Dog dog;
private String thisProperty;
//optionally @Autowire
public ExampleClass(final Dog dog, @Value("${this.property.value}") final String thisProperty){
this.dog = dog;
this.thisProperty = thisProperty;
}
...other stuff...
}
通过这个实现,spring 将知道自动注入什么,但是对于单元测试,你可以做任何你需要的事情。例如使用 spring 自动装配每个依赖项,并通过构造函数手动注入它们以创建“ExampleClass”实例,或者仅使用带有测试属性文件的 spring,或者根本不使用 spring 而自己创建所有对象。
我使用了下面的代码,它对我有用:
@InjectMocks
private ClassNotify classNotify;
@BeforeEach
void init() {
closeable = MockitoAnnotations.openMocks(this);
ReflectionTestUtils.setField(classNotify, "EventType", "test-event");
}
在 springboot 2.4.1 中,我刚刚在我的测试中添加了注释 @SpringBootTest
,显然,在我的 src/test/resources/application.yml
中设置了 spring.profiles.active = test
我将 @ExtendWith({SpringExtension.class})
和 @ContextConfiguration(classes = {RabbitMQ.class, GenericMapToObject.class, ModelMapper.class, StringUtils.class})
用于外部配置
Spring Boot 自动为我们做了很多事情,但是当我们使用注解 @SpringBootTest
时,我们认为一切都会被 Spring boot 自动解决。
有很多文档,但最少的是选择一个 engine (@RunWith(SpringRunner.class)
) 并指明将用于创建 context 以加载配置的类( resources/applicationl.properties
)。
以一种简单的方式,您需要引擎和上下文:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyClassTest .class)
public class MyClassTest {
@Value("${my.property}")
private String myProperty;
@Test
public void checkMyProperty(){
Assert.assertNotNull(my.property);
}
}
当然,如果您查看 Spring Boot 文档,您会发现数以千计的操作系统方法可以做到这一点。
org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
@Value
注释移动到构造函数参数。这使得手动编写代码时测试代码变得更加简单,Spring Boot 不在乎。