如何使用 Mockito 在 Spring 中模拟自动连接的@Value场?

How do I mock an autowired @Value field in Spring with Mockito?

提问人:Dave 提问时间:4/19/2014 最后编辑:krygerDave 更新时间:10/26/2023 访问量:302937

问:

我正在使用 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

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

Spring Mockito 自动连线 值初始化

评论


答:

294赞 geoand 4/19/2014 #1

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

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

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

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

更新

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

评论

22赞 Michał Stochmal 10/29/2018
以防链接失效:在测试期间调用方法之前使用。ReflectionTestUtils.setField(bean, "fieldName", "value");bean
4赞 Antony Sampath Kumar Reddy 12/2/2019
模拟从属性文件中检索的属性的良好解决方案。
1赞 Akhil Surapuram 4/20/2020
@Micha łStochmal ,这样做将产生,因为 filed 是私有的 java.lang.IllegalStateException:无法访问方法:class org.springframework.util.ReflectionUtils 无法访问类 com.kaleidofin.app.service.impl.CVLKRAProvider 的成员,修饰符 “” at org.springframework.util.ReflectionUtils.handleReflectionException(ReflectionUtils.java:112) at org.springframework.util.ReflectionUtils.setField(ReflectionUtils.java:655)
0赞 Ayush Kumar 6/25/2021
当您想要测试使用变量顶部的注释访问属性的类时,这很好用。@Value("${property.name}")private
0赞 Prateek 9/9/2021
我们如何使用 mockito 进行嘲笑?@Value("#{${patientTypes}}") private Map<String, Integer> patientTypes;
37赞 Manuel Quinones 4/23/2014 #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);
   }
}

评论

0赞 Frank Why 3/17/2021
如果您需要为每个测试使用不同的配置,有什么方法可以使用它吗?
178赞 BAERUS 3/30/2016 #3

现在是我第三次用谷歌搜索这个 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")

评论

0赞 Chandan Kumar 1/10/2023
谢谢。经过数小时的调试后,它有所帮助。
43赞 Mark 3/2/2018 #4

我想提出一个相关的解决方案,即将带注释的字段作为参数传递给构造函数,而不是使用类。@ValueReflectionTestUtils

取而代之的是:

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赞 Nils Rommelfanger 3/7/2019
缺点:如果有人弄乱了注释,例如使用属性“bar”而不是“foo”,您的测试仍然有效。我只是有这个案子。
0赞 Marquee 8/20/2019
@NilsEl-Himoud 对于 OP 问题来说,这通常是一个公平的观点,但您提出的问题与使用反射 utils 与构造函数相比并没有更好或更糟。这个答案的要点是考虑构造函数而不是反射 util(公认的答案)。马克,感谢您的回答,我很欣赏这种调整的轻松性和清洁性。
0赞 Ousama 10/11/2022
@Marquee你应该像这样将一个对象的实例传递给setField:ReflectionTestUtils.setField(foo, "foo", "foo");
0赞 Uvuvwevwevwe 8/31/2023
我喜欢这个解决方案!
71赞 Thibault 10/3/2018 #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;
   ...

评论

6赞 afe 11/12/2020
这应该是公认的答案。我自己参考的一点是:你需要模拟你正在使用的所有@Values,你不能模拟第一个,然后从属性中注入第二个。
0赞 Kristijan Iliev 8/17/2022
这对我有用,但是您如何仅为一个测试用例而不是整个测试类设置属性值?
0赞 Thibault 9/8/2022
@KristijanIliev :是的,这个注解适用于整个测试类。如果要使用不同的值,则必须手动更改它,例如使用 ReflectionTestUtils.setField。
2赞 Kostadin Mehomiyski 11/23/2022
对于服务层的单元测试,我们通常使用 JUnit5。在这些情况下,与解决方案相比,建议的方法可能更复杂,因为我们需要手动注入所有模拟,而不是使用@InjectMocks。更简单的解决方案是在测试服务的构造函数中自动复制依赖项和值,并手动实例化被测服务 -@ExtendWith(MockitoExtension.class)ReflectionTestUtils.setField(bean, "fieldName", "value")Service serviceUnderTest = new ServiceImpl(Integer, String, RepositoryMock, OtherServiceMock)
13赞 Dherik 10/24/2018 #6

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

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

因此,您可以使用构造函数传递任何 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");

评论

0赞 jmsalcido 2/15/2022
我认为这是最好的答案,最好解释为什么最好不要对测试属性进行反思,因为我的团队使用作为一种实践,我现在在 Kotlin 和构造函数方面遇到了问题。但感谢您分享这个答案。@Value@InjectMocks
1赞 Bruno Miguel 9/26/2022
这是最好的答案,因为这不仅可以解决您的需求,还可以强制您使用最佳实践进行编程
1赞 SGuru 10/4/2022
这是最简单的工作解决方案。
31赞 Naik Ashwini 6/14/2019 #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/

评论

0赞 not-a-bug 7/29/2020
我做了同样的事情,但它仍然没有反映出来
1赞 Ajay Takur 4/21/2022
@Mendon Ashwini 链接损坏,请修复。
1赞 Olivier Tonglet 11/25/2020 #8

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

我知道这违背了我们在学校学到的标准封装实践。但是,一旦团队中达成了某种共识,我就发现这是最务实的解决方案。

1赞 luke 1/24/2023 #9

另一种方法是使用注释字段。@SpringBootTestproperties

这里我们覆盖属性:example.firstProperty

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

如您所见:它仅覆盖一个属性。未提及的物业保持不变。因此,当我们只需要覆盖测试的特定属性时,这是一个很好的解决方案。@SpringBootTest

对于单个属性,您可以不带大括号地编写它:

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

答案来自: https://www.baeldung.com/spring-tests-override-properties#springBootTest

我还鼓励您尽可能在构造函数中将属性作为参数传递,例如在 Dherik 答案 (https://stackoverflow.com/a/52955459/1673775) 中,因为它使您能够在单元测试中轻松模拟属性。

但是,在集成测试中,您通常不会手动创建对象,而是:

  • 您使用@Autowired
  • 您希望间接修改集成测试中使用的类中使用的属性,因为它是某些直接使用的类的深层依赖关系。

那么这个解决方案可能会有所帮助。@SpringBootTest

0赞 ajay datla 10/26/2023 #10

如果没有任何效果,那么尝试 JAVA Reflection API,我们将获取目标类的实例,然后使私有字段可访问,然后分配它的值 -

以下是需要 财产。UserServiceImpl

public class UserServiceImpl{

    @Value("${service.username}")
    private String username;

}

接下来,我们将看到测试类:

@SpringBootTest
@ContextConfiguration
public class PermissionServiceTests {

  @InjectMocks
    private UserServiceImpl userServiceImpl;
  
  @Test
  public void testLogin() {
      Field field = userServiceImpl.getClass().getDeclaredField("username");
      field.setAccessible(true);
      field.set(userServiceImpl, "ajaydatla");
    }
}