![](/img/trans.png)
[英]Populating database with test data during integration tests with Spring Boot
[英]Populating Spring @Value during Unit Test
我正在嘗試為我的程序中用於驗證 forms 的簡單 bean 編寫單元測試。 該 bean 使用@Component
進行注釋,並具有一個 class 變量,該變量使用初始化
@Value("${this.property.value}") private String thisProperty;
我想為此 class 中的驗證方法編寫單元測試,但是,如果可能的話,我想在不使用屬性文件的情況下這樣做。 我的理由是,如果我從屬性文件中提取的值發生變化,我希望這不會影響我的測試用例。 我的測試用例是測試驗證值的代碼,而不是值本身。
Is there a way to use Java code inside my test class to initialize a Java class and populate the Spring @Value property inside that class then use that to test with?
我確實發現這個How To似乎很接近,但仍然使用屬性文件。 我寧願都是 Java 代碼。
如果可能的話,我會嘗試在沒有 Spring Context 的情況下編寫這些測試。 如果你在沒有 spring 的測試中創建這個類,那么你可以完全控制它的字段。
要設置@value
字段,您可以使用 Springs ReflectionTestUtils
- 它有一個方法setField
來設置私有字段。
@see 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
注釋初始化測試。 更多信息在這里
在 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
。 明天你可以在那個類中聲明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
注釋的類中更相關,因為我們不想要一個帶有太多參數的構造函數。
如果需要,您仍然可以在 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());
}
}
@ExtendWith(SpringExtension.class) // @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
可能會有所幫助。 關鍵是 ConfigDataApplicationContextInitializer 獲取所有道具數據
這是一個很老的問題,我不確定當時它是否是一個選項,但這就是為什么我總是更喜歡構造函數而不是值的 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})
進行外部配置
在test method
里面需要添加以下代碼_
@Test
public void testIsValidFile() {
AnyClass anyClass = new AnyClass();
ReflectionTestUtils.setField(anyClass, "fieldName", "value");
.........
.........
}
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 文檔,您會發現數以千計的操作系統方法可以做到這一點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.