模拟中的弹簧值注入
Posted
技术标签:
【中文标题】模拟中的弹簧值注入【英文标题】:Spring value injection in mockito 【发布时间】:2012-03-01 20:52:43 【问题描述】:我正在尝试为以下方法编写测试类
public class CustomServiceImpl implements CustomService
@Value("#myProp['custom.url']")
private String url;
@Autowire
private DataService dataService;
我在类中的一种方法中使用注入的 url 值。 为了测试这一点,我编写了一个 junit 类
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-test.xml" )
public CustomServiceTest
private CustomService customService;
@Mock
private DataService dataService;
@Before
public void setup()
customService = new CustomServiceImpl();
Setter.set(customService, "dataService", dataService);
...
public class Setter
public static void set(Object obj, String fieldName, Object value) throws Exception
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
在 applicationContext-test.xml 中,我正在使用
加载属性文件 <util:properties id="myProp" location="myProp.properties"/>
但是在运行测试时,url 值没有加载到 CustomService 中。 我想知道是否有办法完成这项工作。
谢谢
【问题讨论】:
如果你在嘲笑CustomService
,那么CustomServiceImpl
是如何被使用的呢?这没有意义。
我正在使用CustomeServiceImpl。更新了代码。如何模拟需要从属性文件中读取的 url 的值?
就目前而言,您没有使用 Spring 设置 customService 值,而是使用以下代码在 setup() 方法中手动设置值:customService = new CustomServiceImpl();
不,我没有使用 spring 来设置测试的 customService 值,但在实际应用程序中,我使用 spring 来设置值。是否可以像我为 dataService 那样模拟 url 的值?
【参考方案1】:
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public CustomServiceTest
@InjectMocks
private CustomServiceImpl customService;
@Mock
private DataService dataService;
@Before
public void setup()
ReflectionTestUtils.setField(customService, "url", "http://someurl");
...
【讨论】:
您能否也用几句话解释您的解决方案? 来自 Rohit 的问题可能还有其他问题,但是在为我的问题寻找解决方案时,这个解决方案正是我想要的,谢谢 Robert 和groovy一样吗? 是的,它在 groovy 中应该是相同的,除了 java 和 groovy 之间的一些细微语法差异。所有的 spring 库都应该可以在 groovy 项目中使用@InjectMock
最后没有“s”?这东西是哪个包的?【参考方案2】:
我同意@skaffman 的评论。
除了您的测试使用MockitoJUnitRunner
,因此它不会寻找任何Spring 的东西,这唯一的目的是初始化Mockito 模拟。 ContextConfiguration
不足以用弹簧连接东西。从技术上讲,如果您想要与弹簧相关的东西,您可以使用 JUnit:SpringJUnit4ClassRunner
。
在您编写单元测试时,您可能需要重新考虑使用 spring。在单元测试中使用弹簧接线是错误的。但是,如果您改为编写集成测试,那么为什么要在那里使用 Mockito,这没有意义(正如 skaffman 所说)!
编辑:现在在您的代码中,您可以在 before 块中直接设置 CustomerServiceImpl
,这也没有任何意义。那里根本不涉及 Spring!
@Before
public void setup()
customService = new CustomServiceImpl();
Setter.set(customService, "dataService", dataService);
编辑 2:如果你想写一个CustomerServiceImpl
的单元测试,那么避免使用 Spring 的东西并直接注入属性的值。您也可以使用 Mockito 将 DataService
模拟直线注入测试实例。
@RunWith(MockitoJUnitRunner.class)
public CustomServiceImplTest
@InjectMocks private CustomServiceImpl customServiceImpl;
@Mock private DataService dataService;
@Before void inject_url() customServiceImpl.url = "http://...";
@Test public void customerService_should_delegate_to_dataService() ...
您可能已经注意到,我使用的是对url
字段的直接访问,该字段可以是包可见的。这是一个实际注入 URL 值的测试解决方法,因为 Mockito 仅注入模拟。
【讨论】:
“重新考虑使用spring”的意思是重新考虑使用Spring配置文件来为this test做依赖注入,对吧?你并不是建议他放弃在他的应用程序中使用 Spring。 @jhericks 不,我的意思是在单元测试中删除与 Spring 相关的代码。我通常对此持强硬态度:我不鼓励在任何单元测试中使用胶合代码,例如 Sprin。 单元测试 的目的是单独测试生产代码(此处为CustomerServiceImpl
)。这实际上似乎是问题作者的意图。如果出于某种原因需要 spring,那么它开始是一个 集成测试,它在测试中没有相同的含义。【参考方案3】:
您可以自动装配到 mutator(setter)中,而不仅仅是注释私有字段。然后你也可以从你的测试类中使用那个设置器。无需公开,包私有即可,因为 Spring 仍然可以访问它,但否则只有您的测试可以进入那里(或同一包中的其他代码)。
@Value("#myProp['custom.url']")
String setUrl( final String url )
this.url = url;
我不喜欢仅仅为了测试而以不同的方式(与我的代码库相比)自动装配,但是从测试中更改被测类的替代方法简直是邪恶的。
【讨论】:
你知道这是否也适用于@Resource
注释?【参考方案4】:
你不应该嘲笑你正在尝试测试的东西。这是没有意义的,因为你不会接触任何你试图测试的代码。而是从上下文中获取CustomerServiceImpl
的实例。
【讨论】:
不要犯单元测试的大罪。【参考方案5】:我有一个从属性文件中读取的字符串列表。 @Before 块中使用的 ReflectionTestUtils 类 setField 方法帮助我在执行测试之前设置这些值。即使对于依赖于 Common DaoSupport 类的我的 dao 层,它也能完美运行。
@Before
public void setList()
List<String> mockedList = new ArrayList<>();
mockedSimList.add("CMS");
mockedSimList.add("SDP");
ReflectionTestUtils.setField(mockedController, "ActualListInController",
mockedList);
【讨论】:
【参考方案6】:您可以使用这个小实用程序类 (gist) 自动将字段值注入目标类:
public class ValueInjectionUtils
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER =
new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX,
SystemPropertyUtils.VALUE_SEPARATOR, true);
public static void injectFieldValues(Object testClassInstance, Properties properties)
for (Field field : FieldUtils.getFieldsListWithAnnotation(testClassInstance.getClass(), Value.class))
String value = field.getAnnotation(Value.class).value();
if (value != null)
try
Object resolvedValue = resolveValue(value, properties);
FieldUtils.writeField(field, testClassInstance, CONVERSION_SERVICE.convert(resolvedValue, field.getType()),
true);
catch (IllegalAccessException e)
throw new IllegalStateException(e);
private static Object resolveValue(String value, Properties properties)
String replacedPlaceholderString = PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(value, properties);
return evaluateSpEL(replacedPlaceholderString, properties);
private static Object evaluateSpEL(String value, Properties properties)
Expression expression = EXPRESSION_PARSER.parseExpression(value, new TemplateParserContext());
EvaluationContext context =
SimpleEvaluationContext.forPropertyAccessors(new MapAccessor()).withRootObject(properties).build();
return expression.getValue(context);
它使用org.apache.commons.lang3.reflect.FieldUtils
访问所有带有@Value
注释的字段,然后使用Spring 实用程序类来解析所有占位符值。如果您想使用自己的 PlaceholderResolver,您还可以将参数类型 properties
更改为 PlaceholderResolver
。
在您的测试中,您可以使用它来注入一组作为Map
或Properties
实例给出的值,如下例所示:
HashMap<String, Object> props = new HashMap<>();
props.put("custom.url", "http://some.url");
Properties properties = new Properties();
properties.put("myProp", props);
ValueInjectionUtils.injectFieldValues(testTarget, properties);
这将尝试解析您的dataService
中的所有@Value
注释字段。我个人更喜欢这个解决方案而不是 ReflectionTestUtils.setField(dataService, "field", "value");
,因为您不必依赖硬编码的字段名称。
【讨论】:
以上是关于模拟中的弹簧值注入的主要内容,如果未能解决你的问题,请参考以下文章