258

I'm using Spring 3.1.4.RELEASE and Mockito 1.9.5. In my Spring class I have:

@Value("#{myProps['default.url']}")
private String defaultUrl;

@Value("#{myProps['default.password']}")
private String defaultrPassword;

// ...

From my JUnit test, which I currently have set up like so:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest 
{ 

I would like to mock a value for my "defaultUrl" field. Note that I don't want to mock values for the other fields — I'd like to keep those as they are, only the "defaultUrl" field. Also note that I have no explicit "setter" methods (e.g. setDefaultUrl) in my class and I don't want to create any just for the purposes of testing.

Given this, how can I mock a value for that one field?

kryger
  • 12,906
  • 8
  • 44
  • 65
Dave
  • 15,639
  • 133
  • 442
  • 830

9 Answers9

287

You can use the magic of Spring's ReflectionTestUtils.setField in order to avoid making any modifications whatsoever to your code.

The comment from Michał Stochmal provides an example:

use ReflectionTestUtils.setField(bean, "fieldName", "value"); before invoking your bean method during test.

Check out this tutorial for even more information, although you probably won't need it since the method is very easy to use

UPDATE

Since the introduction of Spring 4.2.RC1 it is now possible to set a static field without having to supply an instance of the class. See this part of the documentation and this commit.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
geoand
  • 60,071
  • 24
  • 172
  • 190
  • 22
    Just in case of link being dead: use `ReflectionTestUtils.setField(bean, "fieldName", "value");` before invoking your `bean` method during test. – Michał Stochmal Oct 29 '18 at 09:14
  • 4
    Good solution for mocking the properties that are retrieving from the properties file. – Antony Sampath Kumar Reddy Dec 02 '19 at 06:46
  • 1
    @MichałStochmal , doing that will produce since filed is private java.lang.IllegalStateException: Could not access method: Class org.springframework.util.ReflectionUtils can not access a member of class com.kaleidofin.app.service.impl.CVLKRAProvider with modifiers "" at org.springframework.util.ReflectionUtils.handleReflectionException(ReflectionUtils.java:112) at org.springframework.util.ReflectionUtils.setField(ReflectionUtils.java:655) – Akhil Surapuram Apr 20 '20 at 04:03
  • This works fine when you want to test a class that has accessed properties using `@Value("${property.name}")` annotation top of a `private` variable. – Ayush Kumar Jun 25 '21 at 14:36
  • How can we mock `@Value("#{${patientTypes}}") private Map patientTypes;` using mockito? – PAA Sep 09 '21 at 06:32
  • terrible idea to recommend this in 2022. Reflection usage is a code smell – alex Oct 11 '22 at 00:32
  • For unit tests of service layer (e.g, classes tested with `@ExtendWith(MockitoExtension.class)`), a better solution would be to autowire `dependencies` and `values` in the constructor of the tested service, and manually instantiate the service under test. Something like `Service serviceUnderTest = new ServiceImpl(Integer, String, RepositoryMock, OtherServiceMock)`. This is the recommended approach when designing classes that needs to be testable. The downside is that you won't be able to simply use `@InjectMocks`, but it doesn't seem like a huge overhead. – Kostadin Mehomiyski Nov 23 '22 at 15:18
  • What about not using `@RunWith(SpringJUnit4ClassRunner.class)` and only the `@RunWith(MockitoJUnitRunner.class)`. How do you do it without loading the whole context? – Kris Rott May 03 '23 at 10:29
173

It was now the third time I googled myself to this SO post as I always forget how to mock an @Value field. Though the accepted answer is correct, I always need some time to get the "setField" call right, so at least for myself I paste an example snippet here:

Production class:

@Value("#{myProps[‘some.default.url']}")
private String defaultUrl;

Test class:

import org.springframework.test.util.ReflectionTestUtils;

ReflectionTestUtils.setField(instanceUnderTest, "defaultUrl", "http://foo");
// Note: Don't use MyClassUnderTest.class, use the instance you are testing itself
// Note: Don't use the referenced string "#{myProps[‘some.default.url']}", 
//       but simply the FIELDs name ("defaultUrl")
eebbesen
  • 5,070
  • 8
  • 48
  • 70
BAERUS
  • 4,009
  • 3
  • 24
  • 39
69

You can use this magic Spring Test annotation :

@TestPropertySource(properties = { "my.spring.property=20" }) 

see org.springframework.test.context.TestPropertySource

For example, this is the test class :

@ContextConfiguration(classes = { MyTestClass.Config.class })
@TestPropertySource(properties = { "my.spring.property=20" })
public class MyTestClass {

  public static class Config {
    @Bean
    MyClass getMyClass() {
      return new MyClass ();
    }
  }

  @Resource
  private MyClass myClass ;

  @Test
  public void myTest() {
   ...

And this is the class with the property :

@Component
public class MyClass {

  @Value("${my.spring.property}")
  private int mySpringProperty;
   ...
Thibault
  • 1,049
  • 9
  • 7
  • 6
    this should be the accepted answer. One note for my own reference: you need to mock all the @Values you are using, you cannot mock a first one and then inject a second one from properties. – afe Nov 12 '20 at 10:18
  • This worked for me, but how do you set a property value only for one test case, not the entire test class? – Kristijan Iliev Aug 17 '22 at 14:34
  • @KristijanIliev : yes, this annotation applies to the whole test class. If you want to use differents values you have to change it manually, using ReflectionTestUtils.setField for example. – Thibault Sep 07 '22 at 20:14
  • 2
    For unit tests of the service layer we usually use `@ExtendWith(MockitoExtension.class)` with JUnit5. In those cases suggested approach might be more complex to implement compared to `ReflectionTestUtils.setField(bean, "fieldName", "value")` solution, because we would need to manually inject all mocks instead of using @InjectMocks. Even simpler solution would be to autowiere dependencies and values in the constructor of the tested service, and manually instantiate the service under test - `Service serviceUnderTest = new ServiceImpl(Integer, String, RepositoryMock, OtherServiceMock)` – Kostadin Mehomiyski Nov 23 '22 at 15:14
42

I'd like to suggest a related solution, which is to pass the @Value-annotated fields as parameters to the constructor, instead of using the ReflectionTestUtils class.

Instead of this:

public class Foo {

    @Value("${foo}")
    private String foo;
}

and

public class FooTest {

    @InjectMocks
    private Foo foo;

    @Before
    public void setUp() {
        ReflectionTestUtils.setField(Foo.class, "foo", "foo");
    }

    @Test
    public void testFoo() {
        // stuff
    }
}

Do this:

public class Foo {

    private String foo;

    public Foo(@Value("${foo}") String foo) {
        this.foo = foo;
    }
}

and

public class FooTest {

    private Foo foo;

    @Before
    public void setUp() {
        foo = new Foo("foo");
    }

    @Test
    public void testFoo() {
        // stuff
    }
}

Benefits of this approach: 1) we can instantiate the Foo class without a dependency container (it's just a constructor), and 2) we're not coupling our test to our implementation details (reflection ties us to the field name using a string, which could cause a problem if we change the field name).

Mark
  • 4,970
  • 5
  • 42
  • 66
  • 3
    downside: If someone messes with the annotation, e.g. uses a property 'bar' instead of 'foo', your test will still work. I just have this case. – Nils Rommelfanger Mar 07 '19 at 07:47
  • @NilsEl-Himoud That's a fair point in general for the OP question, but the issue you raise isn't any better or worse off using reflection utils vs constructor. The point of this answer was consideration of constructor over reflection util (the accepted answer). Mark, thanks for the answer, I appreciate the ease and cleanliness of this tweak. – Marquee Aug 20 '19 at 05:25
  • @Marquee you should pass an instance of an object to setField like this : `ReflectionTestUtils.setField(foo, "foo", "foo");` – Ousama Oct 11 '22 at 07:06
  • I like this solution! – Uvuvwevwevwe Aug 31 '23 at 06:50
37

You can also mock your property configuration into your test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class MyTest 
{ 
   @Configuration
   public static class MockConfig{
       @Bean
       public Properties myProps(){
             Properties properties = new Properties();
             properties.setProperty("default.url", "myUrl");
             properties.setProperty("property.value2", "value2");
             return properties;
        }
   }
   @Value("#{myProps['default.url']}")
   private String defaultUrl;

   @Test
   public void testValue(){
       Assert.assertEquals("myUrl", defaultUrl);
   }
}
Manuel Quinones
  • 4,196
  • 1
  • 19
  • 19
31

I used the below code and it worked for me:

@InjectMocks
private ClassABC classABC;

@Before
public void setUp() {
    ReflectionTestUtils.setField(classABC, "constantFromConfigFile", 3);
}

Reference: https://www.jeejava.com/mock-an-autowired-value-field-in-spring-with-junit-mockito/

Naik Ashwini
  • 750
  • 12
  • 32
12

Also note that I have no explicit "setter" methods (e.g. setDefaultUrl) in my class and I don't want to create any just for the purposes of testing.

One way to resolve this is change your class to use Constructor Injection, that can be used for testing and Spring injection. No more reflection :)

So, you can pass any String using the constructor:

class MySpringClass {

    private final String defaultUrl;
    private final String defaultrPassword;

    public MySpringClass (
         @Value("#{myProps['default.url']}") String defaultUrl, 
         @Value("#{myProps['default.password']}") String defaultrPassword) {
        this.defaultUrl = defaultUrl;
        this.defaultrPassword= defaultrPassword;
    }

}

And in your test, just use it:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
Dherik
  • 17,757
  • 11
  • 115
  • 164
  • I think this is the best answer, it would be better to explain why it's better to not have reflection on the test properties tho, I am having issues right now with Kotlin and `@Value` constructors since my team uses `@InjectMocks` as a practice. But thanks for sharing this answer. – jmsalcido Feb 14 '22 at 18:56
  • 1
    This is the best answer as this not only solves your needs as its enforces you to program with best practices – Bruno Miguel Sep 26 '22 at 14:50
  • 1
    This is the simplest working solution. – SGuru Oct 03 '22 at 17:58
1

Whenever possible, I set the field visibility as package-protected so it can be accessed from the test class. I document that using Guava's @VisibleForTesting annotation (in case the next guy wonders why it's not private). This way I don't have to rely on the string name of the field and everything stays type-safe.

I know it goes against standard encapsulation practices we were taught in school. But as soon as there is some agreement in the team to go this way, I found it the most pragmatic solution.

Olivier Tonglet
  • 3,312
  • 24
  • 40
1

Another way is to use @SpringBootTest annotation properties field.

Here we override example.firstProperty property:

@SpringBootTest(properties = { "example.firstProperty=annotation" })
public class SpringBootPropertySourceResolverIntegrationTest {

    @Autowired private PropertySourceResolver propertySourceResolver;

    @Test
    public void shouldSpringBootTestAnnotation_overridePropertyValues() {
        String firstProperty = propertySourceResolver.getFirstProperty();
        String secondProperty = propertySourceResolver.getSecondProperty();

        Assert.assertEquals("annotation", firstProperty);
        Assert.assertEquals("defaultSecond", secondProperty);
    }
}

As you can see It overrides only one property. Properties not mentioned in @SpringBootTest stay untouched. Therefore, this is a great solution when we need to override only specific properties for the test.

For single property you can write it without braces:

@SpringBootTest(properties = "example.firstProperty=annotation")

Answer from: https://www.baeldung.com/spring-tests-override-properties#springBootTest

I also encourage you to whenever possible pass property as a parameter in constructor like in Dherik answer (https://stackoverflow.com/a/52955459/1673775) as it enables you to mock properties easily in unit tests.

However in integration tests you often don't create objects manually, but:

  • you use @Autowired
  • you want to modify property used in a class that is used in your integration test indirectly as it is deep dependency of some directly used class.

then this solution with @SpringBootTest might be helpful.

luke
  • 3,435
  • 33
  • 41