如何在Spring中使用Mockito模拟自动装配的@Value字段?


123

我正在使用Spring 3.1.4.RELEASE和Mockito 1.9.5。在我的春季班上,我有:

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

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

// ...

从我目前设置的JUnit测试中可以看到:

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

我想为“ defaultUrl”字段模拟一个值。请注意,我不想模拟其他字段的值,我想保留它们的值,只保留“ defaultUrl”字段。另外请注意,setDefaultUrl我的班级中没有明确的“ setter”方法(例如),并且我不想仅为测试目的而创建任何方法。

鉴于此,我该如何模拟该字段的值?

Answers:


144

您可以使用Spring的魔力ReflectionTestUtils.setField来避免对您的代码进行任何修改。

查阅教程以获取更多信息,尽管由于该方法易于使用,您可能不需要它。

更新

自从Spring 4.2.RC1引入以来,现在就可以设置一个静态字段,而不必提供该类的实例。请参阅文档的部分和提交。


12
万一链接失效ReflectionTestUtils.setField(bean, "fieldName", "value");,请bean在测试期间调用方法之前使用。
米歇尔·斯托克马尔(MichałStochmal),

2
模拟从属性文件中检索的属性的好解决方案。
安东尼·萨姆帕特·库马尔·雷迪

@MichałStochmal,这样做将产生,因为归档是私有java.lang.IllegalStateException:无法访问方法:类org.springframework.util.ReflectionUtils无法访问带有修饰符的com.kaleidofin.app.service.impl.CVLKRAProvider类的成员org.springframework.util.ReflectionUtils.setField(ReflectionUtils.java:655)的org.springframework.util.ReflectionUtils.handleReflectionException(ReflectionUtils.java:112)
Akhil Surapuram

112

现在这是我第三次谷歌搜索该SO职位,因为我总是忘记如何模拟@Value字段。尽管接受的答案是正确的,但我总是需要一些时间才能正确完成“ setField”调用,因此至少对我而言,我在此处粘贴了一个示例代码段:

生产类别:

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

测试类别:

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")

32

您也可以将属性配置模拟到测试类中

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

25

我想提出一个相关的解决方案,该方法是将@Value-annotated字段作为参数传递给构造函数,而不是使用ReflectionTestUtils类。

代替这个:

public class Foo {

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

public class FooTest {

    @InjectMocks
    private Foo foo;

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

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

做这个:

public class Foo {

    private String foo;

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

public class FooTest {

    private Foo foo;

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

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

这种方法的好处:1)我们可以实例化Foo类而无需依赖容器(它只是一个构造函数),以及2)我们没有将测试与实现细节耦合(反射使用字符串将我们绑定到字段名称,如果我们更改字段名称,可能会导致问题)。


3
缺点:如果有人弄乱了注释,例如使用属性“ bar”而不是“ foo”,则您的测试仍然可以进行。我只有这种情况。
Nils El-Himoud

@ NilsEl-Himoud对于OP问题,这通常是一个公平的观点,但是使用反射utils与构造函数相比,您提出的问题并没有好坏之分。这个答案的重点是考虑构造函数而不是反射util(公认的答案)。马克,感谢您的回答,我感谢此调整的简便性和整洁性。
字幕

22

您可以使用以下神奇的Spring Test注释:

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

参见 org.springframework.test.context.TestPropertySource

例如,这是测试类:

@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() {
   ...

这是具有属性的类:

@Component
public class MyClass {

  @Value("${my.spring.property}")
  private int mySpringProperty;
   ...


1

还要注意,我在类中没有显式的“ setter”方法(例如setDefaultUrl),并且我不想为测试目的而创建任何方法。

解决此问题的一种方法是将您的类更改为使用用于测试和Spring注入的Constructor Injection。没有更多的反思:)

因此,您可以使用构造函数传递任何String:

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

}

在测试中,只需使用它:

MySpringClass MySpringClass  = new MySpringClass("anyUrl", "anyPassword");
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.