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

Posted

技术标签:

【中文标题】如何使用 Mockito 在 Spring 中模拟自动装配的 @Value 字段?【英文标题】:How do I mock an autowired @Value field in Spring with Mockito? 【发布时间】:2014-06-03 11:22:51 【问题描述】:

我正在使用 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”字段。另请注意,我的班级中没有明确的“setter”方法(例如setDefaultUrl),我不想仅仅为了测试目的而创建任何方法。

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

【问题讨论】:

【参考方案1】:

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

Michał Stochmal 的评论提供了一个例子:

在测试期间调用bean 方法之前使用ReflectionTestUtils.setField(bean, "fieldName", "value");

查看this 教程以获得更多信息,尽管您可能不需要它,因为该方法非常易于使用

更新

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

【讨论】:

万一链接失效:在测试期间调用bean方法之前使用ReflectionTestUtils.setField(bean, "fieldName", "value"); 模拟从属性文件中检索的属性的良好解决方案。 @MichałStochmal ,这样做会产生,因为归档是私有的 java.lang.IllegalStateException: 无法访问方法:类 org.springframework.util.ReflectionUtils 无法访问 com.kaleidofin.app 类的成员.service.impl.CVLKRAProvider 在 org.springframework.util.ReflectionUtils.handleReflectionException(ReflectionUtils.java:112) 中带有修饰符 "" 在 org.springframework.util.ReflectionUtils.setField(ReflectionUtils.java:655) 当你想测试一个使用 @Value("$property.name") 注释顶部的 private 变量访问属性的类时,这很好用。 我们如何使用 mockito 模拟 @Value("#$patientTypes") private Map<String, Integer> patientTypes;【参考方案2】:

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

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

【讨论】:

如果每次测试都需要不同的配置,有什么方法可以使用它吗?【参考方案3】:

这是我第三次用谷歌搜索这个 SO 帖子,因为我总是忘记如何模拟 @Value 字段。虽然接受的答案是正确的,但我总是需要一些时间来正确调用“setField”,所以至少对我自己来说,我在这里粘贴了一个示例 sn-p:

生产类:

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

【讨论】:

【参考方案4】:

我想建议一个相关的解决方案,即将@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)我们不会将测试与实现细节耦合(反射将我们与字段名称联系起来使用一个字符串,如果我们更改字段名称可能会导致问题)。

【讨论】:

缺点:如果有人弄乱了注释,例如使用属性“bar”而不是“foo”,您的测试仍然可以工作。我只有这个案子。 @NilsEl-Himoud 对于 OP 问题来说,这通常是一个公平的观点,但是您提出的问题与使用反射实用程序与构造函数相比并没有好坏之分。这个答案的重点是考虑构造函数而不是反射工具(接受的答案)。马克,谢谢你的回答,我很欣赏这个调整的轻松和整洁。【参考方案5】:

你可以使用这个神奇的 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;
   ...

【讨论】:

这应该是公认的答案。我自己参考的一个注释:你需要模拟你正在使用的所有@Values,你不能模拟第一个然后从属性中注入第二个。【参考方案6】:

另请注意,我的班级中没有明确的“setter”方法(例如 setDefaultUrl),我不想仅仅为了测试目的而创建任何方法。

解决此问题的一种方法是将您的类更改为使用 构造函数注入,它可用于测试和 Spring 注入。没有更多的反思:)

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

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

【讨论】:

【参考方案7】:

我使用了下面的代码,它对我有用:

@InjectMocks
private ClassABC classABC;

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

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

【讨论】:

我做了同样的事情,但仍然没有反映【参考方案8】:

只要可能,我将字段可见性设置为包保护,以便可以从测试类访问它。我使用 Guava 的 @VisibleForTesting 注释记录了这一点(以防下一个人想知道为什么它不是私有的)。这样我就不必依赖字段的字符串名称,并且一切都保持类型安全。

我知道这违反了我们在学校所教的标准封装做法。但一旦团队中达成某种协议,我就发现这是最务实的解决方案。

【讨论】:

以上是关于如何使用 Mockito 在 Spring 中模拟自动装配的 @Value 字段?的主要内容,如果未能解决你的问题,请参考以下文章

Mockito 用 Spring 模拟:“传递给 verify() 的参数不是模拟!”

如何使用 Mockito 模拟由 Spring 应用程序上下文创建的对象以进行单元测试覆盖?

使用 Spring Boot 和 Mockito 模拟对象方法调用

Kotlin,Spring book,Mockito,@InjectMocks,使用与创建的不同的模拟

使用 mockito 为 spring-boot 应用程序模拟 Qualified bean

使用 Mockito 在 REST 控制器中模拟类