Populating Spring @Value during Unit Test

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.


Solution 1:

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)

Solution 2:

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);
  }

}

Solution 3:

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.

Solution 4:

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.