提问人:Dave 提问时间:4/19/2014 最后编辑:krygerDave 更新时间:10/26/2023 访问量:302937
如何使用 Mockito 在 Spring 中模拟自动连接的@Value场?
How do I mock an autowired @Value field in Spring with Mockito?
问:
我正在使用 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的魔力来避免对你的代码进行任何修改。ReflectionTestUtils.setField
Michał Stochmal 的评论提供了一个例子:
在测试期间调用方法之前使用。
ReflectionTestUtils.setField(bean, "fieldName", "value");
bean
查看本教程以获取更多信息,尽管您可能不需要它,因为该方法非常易于使用
更新
自从引入 Spring 4.2.RC1 以来,现在可以设置静态字段,而无需提供类的实例。请参阅文档的这一部分和此提交。
评论
ReflectionTestUtils.setField(bean, "fieldName", "value");
bean
@Value("${property.name}")
private
@Value("#{${patientTypes}}") private Map<String, Integer> patientTypes;
还可以将属性配置模拟到测试类中
@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);
}
}
评论
现在是我第三次用谷歌搜索这个 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")
评论
我想提出一个相关的解决方案,即将带注释的字段作为参数传递给构造函数,而不是使用类。@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)我们没有将测试与实现细节耦合(反射使用字符串将我们与字段名称联系起来,如果我们更改字段名称,可能会导致问题)。
评论
ReflectionTestUtils.setField(foo, "foo", "foo");
您可以使用这个神奇的 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;
...
评论
@ExtendWith(MockitoExtension.class)
ReflectionTestUtils.setField(bean, "fieldName", "value")
Service serviceUnderTest = new ServiceImpl(Integer, String, RepositoryMock, OtherServiceMock)
另请注意,我的类中没有显式的“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");
评论
@Value
@InjectMocks
我使用了以下代码,它对我有用:
@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/
评论
只要有可能,我就会将字段可见性设置为受包保护,以便可以从测试类访问它。我使用 Guava 的@VisibleForTesting注释记录了这一点(以防下一个人想知道为什么它不是私有的)。这样,我就不必依赖字段的字符串名称,并且一切都保持类型安全。
我知道这违背了我们在学校学到的标准封装实践。但是,一旦团队中达成了某种共识,我就发现这是最务实的解决方案。
另一种方法是使用注释字段。@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 答案 (https://stackoverflow.com/a/52955459/1673775) 中,因为它使您能够在单元测试中轻松模拟属性。
但是,在集成测试中,您通常不会手动创建对象,而是:
- 您使用
@Autowired
- 您希望间接修改集成测试中使用的类中使用的属性,因为它是某些直接使用的类的深层依赖关系。
那么这个解决方案可能会有所帮助。@SpringBootTest
如果没有任何效果,那么尝试 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"); } }
评论