Заполнение Spring @Value во время Unit Test
Я пытаюсь написать Unit Test для простого bean, который использовался в моей программе для проверки форм. bean аннотируется с помощью @Component
и имеет переменную класса, которая инициализируется с помощью @Value("${this.property.value}") private String thisProperty;
Я хотел бы написать модульные тесты для методов проверки внутри этого класса, однако, если возможно, я хотел бы сделать это без использования файла свойств. Мое рассуждение в этом заключается в том, что если значение, которое я вытягиваю из файла свойств, меняет, я бы хотел, чтобы это не повлияло на мой тестовый пример. Мой тестовый пример проверяет код, который проверяет значение, а не само значение.
Есть ли способ использовать Java-код внутри моего тестового класса для инициализации класса Java и заполнения свойства Spring @Value внутри этого класса, а затем использовать его для тестирования с помощью <
Я нашел этот How To, который кажется закрытым, но все еще использует файл свойств. Я бы предпочел, чтобы это был Java-код.
Спасибо
Ответы
Ответ 1
Если возможно, я попытаюсь написать те тесты без Spring Context. Если вы создадите этот класс в своем тесте без spring, тогда вы получите полный контроль над его полями.
Чтобы установить поле @value
, вы можете использовать Springs ReflectionTestUtils
- у него есть метод setField
, чтобы установить частные поля.
@see JavaDoc: ReflectionTestUtils.setField(java.lang.Object, java.lang.String, java.lang.Object)
Ответ 2
Поскольку Spring 4.1 вы можете настроить значения свойств только в коде, используя аннотацию org.springframework.test.context.TestPropertySource
на уровне класса Unit Tests. Вы можете использовать этот подход даже для ввода свойств в зависимые 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();
}
}
}
Примечание. Необходимо иметь экземпляр org.springframework.context.support.PropertySourcesPlaceholderConfigurer
в Spring контексте
Редактировать 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);
}
}
Ответ 3
Если вы хотите, вы все равно можете выполнить свои тесты в 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. В этом случае вы вообще не запускаете тесты внутри Spring, что намного проще и проще.
Ответ 4
Не злоупотребляйте приватными полями, чтобы получить/установить с помощью отражения
Использование рефлексии, как это делается в нескольких ответах, - это то, чего мы могли бы избежать.
Это приносит небольшую ценность здесь, в то время как это имеет много недостатков:
- мы обнаруживаем проблемы с отражением только во время выполнения (например, поля больше не существуют)
- Мы хотим инкапсуляцию, но не непрозрачный класс, который скрывает зависимости, которые должны быть видны, и делает класс более непрозрачным и менее тестируемым.
- это поощряет плохой дизайн. Сегодня вы объявляете
@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;
}
//...
}
Пример юнит-теста
Вы можете создать экземпляр Foo
без Spring и ввести любое значение для property
благодаря конструктору:
public class FooTest{
Foo foo = new Foo("dummyValue");
@Test
public void doThat(){
...
}
}
Пример интеграционного теста
Вы можете внедрить свойство в контексте Spring Boot таким простым способом благодаря атрибуту properties
в @SpringBootTest
:
@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{
@Autowired
Foo foo;
@Test
public void doThat(){
...
}
}
Вы можете использовать в качестве альтернативы @TestPropertySource
, но он добавляет дополнительную аннотацию:
@SpringBootTest
@TestPropertySource("property.value=dummyValue")
public class FooTest{ ...}
С Spring (без Spring Boot) все должно быть немного сложнее, но, поскольку я долгое время не использовал Spring без Spring Boot, я не предпочитаю говорить глупости.
В качестве примечания: если вам нужно установить много полей @Value
, то их извлечение в класс, помеченный @ConfigurationProperties
, более уместно, потому что нам не нужен конструктор со слишком большим количеством аргументов.
Ответ 5
Кажется, что это работает, хотя все еще немного многословно (я бы хотел еще что-то поменьше):
@BeforeClass
public static void beforeClass() {
System.setProperty("some.property", "<value>");
}
// Optionally:
@AfterClass
public static void afterClass() {
System.clearProperty("some.property");
}
Ответ 6
Добавление 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());
}
}