可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
You can use the magic of Spring's ReflectionTestUtils.setField
in order to avoid making any modifications whatsoever to your code.
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.
回答2:
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(myClassUnderTest, "defaultUrl", "http://foo");
// Note: Don't use MyClassUnderTest.CLASS, use the class itself
// Note: Don't use the referenced string "#{myProps[‘some.default.url']}",
// but simply the FIELDs name ("defaultUrl")
回答3:
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);
}
}
回答4:
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).
回答5:
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;
...
回答6:
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 is 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");