在单元测试期间填充Spring @Value

我试图为我的程序中用于验证表单的简单bean编写单元测试。bean用@Component注释,并且有一个类变量,用

@Value("${this.property.value}") private String thisProperty;

我想为这个类中的验证方法编写单元测试,但是,如果可能的话,我想这样做而不使用属性文件。我这样做的原因是,如果我从属性文件中提取的值发生了变化,我希望它不影响我的测试用例。我的测试用例是测试验证值的代码,而不是值本身。

是否有一种方法可以在我的测试类中使用Java代码来初始化一个Java类,并在该类中填充Spring @Value属性,然后使用它来测试?

我确实找到了这个如何,似乎是接近的,但仍然使用属性文件。我宁愿全部都是Java代码。

383149 次浏览

如果可能的话,我会尝试在没有Spring Context的情况下编写这些测试。如果您在没有spring的测试中创建这个类,那么您可以完全控制它的字段。

要设置@value字段,你可以使用Springs ReflectionTestUtils -它有一个方法setField来设置私有字段。

@see JavaDoc: ReflectionTestUtils.setField (. lang。对象,. lang。字符串,java . lang . object)

如果您愿意,您仍然可以在Spring Context中运行测试,并在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");
}

在配置中添加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());
}
}

从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的实例是必要的

如果你使用SpringBoot 1.4.0及以后版本,你可以用@SpringBootTest@SpringBootConfiguration注释初始化测试。更多信息在这里

对于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 field。明天你可以在这个类中声明510,你甚至可能没有意识到你减少了类的设计。使用更可见的方法来设置这些字段(比如构造函数),在添加所有这些字段之前,你会三思而后行,你可能会将它们封装到另一个类中并使用@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(){
...
}
}

集成测试示例

多亏了@SpringBootTestproperties属性,你可以用这种简单的方式在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,所以我不喜欢说愚蠢的事情。

旁注:如果你有很多@Value字段要设置,将它们提取到一个带有@ConfigurationProperties注释的类中更相关,因为我们不希望构造函数有太多参数。

Spring Boot自动为我们做了很多事情,但当我们使用@SpringBootTest注释时,我们认为所有事情都将由Spring Boot自动解决。

有很多文档,但最小的是选择一个引擎 (@RunWith(SpringRunner.class)),并指出将用于创建上下文来加载配置(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文档,你会发现数千种操作系统的方法。

这是一个相当老的问题,我不确定当时是否有这个选项,但这就是为什么我总是喜欢通过构造函数而不是通过值来实现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,自己创建所有对象。

在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})外部conf

@ExtendWith(SpringExtension.class)    // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)

希望这能有所帮助。关键是ConfigDataApplicationContextInitializer获取所有道具数据

我使用下面的代码,它为我工作:

@InjectMocks
private ClassNotify classNotify;


@BeforeEach
void init() {
closeable = MockitoAnnotations.openMocks(this);
ReflectionTestUtils.setField(classNotify, "EventType", "test-event");


}

test method中需要添加以下code_

@Test
public void testIsValidFile() {


AnyClass anyClass = new AnyClass();
ReflectionTestUtils.setField(anyClass, "fieldName", "value");
.........
.........
}