如何在Spring中用Mockito模拟一个自动连接的@Value字段?

syqv5f0l  于 2023-01-24  发布在  Spring
关注(0)|答案(9)|浏览(239)

我使用的是Spring 3.1.4.RELEASE和Mockito 1.9.5。在我的Spring类中,我有:

@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),我不想仅仅为了测试的目的而创建任何方法。
既然如此,我如何模拟该字段的值呢?

lx0bsm1f

lx0bsm1f1#

您可以使用Spring的ReflectionTestUtils.setField的魔力来避免对代码进行任何修改。
Michał Stochmal中的注解提供了一个示例:
在测试期间调用bean方法之前使用ReflectionTestUtils.setField(bean, "fieldName", "value");
查看this教程以获得更多信息,不过您可能不需要它,因为该方法非常容易使用

    • 更新**

自从Spring 4.2.RC1引入以来,现在可以设置一个静态字段,而不必提供类的示例。参见文档的这一部分和this commit。

gmol1639

gmol16392#

这是我第三次在谷歌上搜索这个帖子,因为我总是忘记如何模拟一个@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")
xmq68pz9

xmq68pz93#

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

nue99wik4#

我想建议一个相关的解决方案,那就是将带@Value注解的字段作为参数传递给构造函数,而不是使用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)我们没有将测试与实现细节耦合(反射使用字符串将我们与字段名绑定,如果我们更改字段名,这可能会导致问题)。

sqxo8psd

sqxo8psd5#

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

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

62lalag46#

我使用了下面的代码,它为我工作:

@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/

fcy6dtqo

fcy6dtqo7#

还要注意,我的类中没有显式的“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");
ax6ht2ek

ax6ht2ek8#

只要有可能,我就将字段的可见性设置为包保护的,这样就可以从测试类访问它。我使用Guava的@VisibleForTesting注解对此进行了记录(以防下一个人想知道为什么它不是私有的)。这样我就不必依赖字段的字符串名称,一切都保持类型安全。
我知道这违背了我们在学校里学到的标准封装实践,但是一旦团队中达成了某种共识,我就发现这是最实用的解决方案。

5t7ly7z5

5t7ly7z59#

另一种方法是使用@SpringBootTest注解properties字段。

这里我们重写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 answer(https://stackoverflow.com/a/52955459/1673775)中那样,因为它使您能够在单元测试中轻松地模拟属性。
然而,在集成测试中,您通常不会手动创建对象,而是:

  • 是否使用@Autowired
  • 您希望修改在集成测试中间接使用的类中使用的属性,因为它是某个直接使用的类的深度依赖项。

则具有@SpringBootTest的该解决方案可能是有用的。

相关问题