I'm trying to write a Unit Test for a simple bean that's used in my program to validate forms. The bean is annotated with @Component
and has a class variable that is initialized using
@Value("${this.property.value}") private String thisProperty;
I would like to write unit tests for the validation methods inside this class, however, if possible I would like to do so without utilizing the properties file. My reasoning behind this, is that if the value I'm pulling from the properties file changes, I would like that to not affect my test case. My test case is testing the code that validates the value, not the value itself.
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?
I did find this How To that seems to be close, but still uses a properties file. I would rather it all be Java code.
If possible I would try to write those test without Spring Context. If you create this class in your test without spring, then you have full control over its fields.
To set the @value
field you can use Springs ReflectionTestUtils
- it has a method setField
to set private fields.
@see JavaDoc: ReflectionTestUtils.setField(java.lang.Object, java.lang.String, java.lang.Object)
Since Spring 4.1 you could set up property values just in code by using org.springframework.test.context.TestPropertySource
annotation on Unit Tests class level. You could use this approach even for injecting properties into dependent bean instances
For example
@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();
}
}
}
Note: It's necessary to have instance of org.springframework.context.support.PropertySourcesPlaceholderConfigurer
in Spring context
Edit 24-08-2017: If you are using SpringBoot 1.4.0 and later you could initialize tests with @SpringBootTest
and @SpringBootConfiguration
annotations. More info here
In case of SpringBoot we have following code
@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);
}
}
Don't abuse private fields get/set by reflection
Using reflection as that is done in several answers here is something that we could avoid. It brings a small value here while it presents multiple drawbacks :
we detect reflection issues only at runtime (ex: fields not existing any longer)
We want encapsulation but not a opaque class that hides dependencies that should be visible and make the class more opaque and less testable.
it encourages bad design. Today you declare a @Value String field. Tomorrow you can declare 5 or 10 of them in that class and you may not even be straight aware that you decrease the design of the class. With a more visible approach to set these fields (such as constructor) , you will think twice before adding all these fields and you will probably encapsulate them into another class and use @ConfigurationProperties.
Make your class testable both unitary and in integration
To be able to write both plain unit tests (that is without a running spring container) and integration tests for your Spring component class, you have to make this class usable with or without Spring. Running a container in an unit test when it is not required is a bad practice that slows down local builds : you don't want that. I added this answer because no answer here seems to show this distinction and so they rely on a running container systematically.
So I think that you should move this property defined as an internal of the class :
@Component
public class Foo{
@Value("${property.value}") private String property;
//...
}
into a constructor parameter that will be injected by Spring :
@Component
public class Foo{
private String property;
public Foo(@Value("${property.value}") String property){
this.property = property;
}
//...
}
Unit test example
You can instantiate Foo
without Spring and inject any value for property
thanks to the constructor :
public class FooTest{
Foo foo = new Foo("dummyValue");
@Test
public void doThat(){
...
}
}
Integration test example
You can injecting the property in the context with Spring Boot in this simple way thanks to the properties
attribute of @SpringBootTest
:
@SpringBootTest(properties="property.value=dummyValue")
public class FooTest{
@Autowired
Foo foo;
@Test
public void doThat(){
...
}
}
You could use as alternative @TestPropertySource
but it adds an additional annotation :
@SpringBootTest
@TestPropertySource(properties="property.value=dummyValue")
public class FooTest{ ...}
With Spring (without Spring Boot), it should be a little more complicated but as I didn't use Spring without Spring Boot from a long time I don't prefer say a stupid thing.
As a side note : if you have many @Value
fields to set, extracting them into a class annotated with @ConfigurationProperties
is more relevant because we don't want a constructor with too many arguments.
final
, i.e private String final property
@Value
you propose to create a constructor with 10 parameters?
If you want, you can still run your tests within Spring Context and set the required properties inside Spring configuration class. If you use JUnit, use SpringJUnit4ClassRunner and define dedicated configuration class for your tests like that:
The class under test:
@Component
public SomeClass {
@Autowired
private SomeDependency someDependency;
@Value("${someProperty}")
private String someProperty;
}
The test class:
@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() { ... }
}
And the configuration class for this test:
@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);
}
}
Having that said, I wouldn't recommend this approach, I just added it here for reference. In my opinion much better way is to use Mockito runner. In that case you don't run tests inside Spring at all, which is much more clear and simpler.
This seems to work, although still a bit verbose (I'd like something shorter still):
@BeforeClass
public static void beforeClass() {
System.setProperty("some.property", "<value>");
}
// Optionally:
@AfterClass
public static void afterClass() {
System.clearProperty("some.property");
}
@TestProperty
annotation.
@Value
s, regardless of whether the corresponding property is set or not.
Adding PropertyPlaceholderConfigurer in configuration is working for me.
@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;
}
}
And in test class
@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)
May that could help. The key is ConfigDataApplicationContextInitializer get all props datas
Its quite old question, and I'm not sure if it was an option at that time, but this is the reason why I always prefer DependencyInjection by the constructor than by the value.
I can imagine that your class might look like this:
class ExampleClass{
@Autowired
private Dog dog;
@Value("${this.property.value}")
private String thisProperty;
...other stuff...
}
You can change it to:
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...
}
With this implementation, the spring will know what to inject automatically, but for unit testing, you can do whatever you need. For example Autowire every dependency wth spring, and inject them manually via constructor to create "ExampleClass" instance, or use only spring with test property file, or do not use spring at all and create all object yourself.
I used the below code and it worked for me:
@InjectMocks
private ClassNotify classNotify;
@BeforeEach
void init() {
closeable = MockitoAnnotations.openMocks(this);
ReflectionTestUtils.setField(classNotify, "EventType", "test-event");
}
In springboot 2.4.1 im just added annotation @SpringBootTest
in my test, and obviously, setted spring.profiles.active = test
in my src/test/resources/application.yml
Im using @ExtendWith({SpringExtension.class})
and @ContextConfiguration(classes = {RabbitMQ.class, GenericMapToObject.class, ModelMapper.class, StringUtils.class})
for external confs
Spring Boot do a lot of automatically things to us but when we use the annotation @SpringBootTest
we think that everything will be automatically solved by Spring boot.
There are a lot of documentation, but the minimal is to choose one engine (@RunWith(SpringRunner.class)
) and indicate the class that will be used create the context to load the configuration (resources/applicationl.properties
).
In a simple way you need the engine and the context:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyClassTest .class)
public class MyClassTest {
@Value("${my.property}")
private String myProperty;
@Test
public void checkMyProperty(){
Assert.assertNotNull(my.property);
}
}
Of course, if you look the Spring Boot documentation you will find thousands os ways to do that.
Success story sharing
org.springframework.test.util.ReflectionTestUtils.setField(classUnderTest, "field", "value");
@Value
annotation to the constructor parameter. This makes the test code much simpler when writing the code manually, and Spring Boot doesn't care.