我正在尝试为以下方法编写测试类
public class CustomServiceImpl implements CustomService {
@Value("#{myProp['custom.url']}")
private String url;
@Autowire
private DataService dataService;
我正在类中的一个方法中使用注入的url值。为了测试这一点,我写了一个三年级的
@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中。我想知道是否有办法完成这项工作。
感谢
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");
}
...
}
我同意@skaffman的评论。
除了你的测试使用MockitoJUnitRunner
,因此它不会寻找任何Spring的东西,唯一的目的是初始化Mockito mock。ContextConfiguration
不足以将东西与弹簧连接起来。从技术上讲,使用JUnit,如果您想要与spring相关的东西,可以使用以下runner:SpringJUnit4ClassRunner
。
此外,在编写单元测试时,您可能需要重新考虑spring的使用。在单元测试中使用弹簧接线是错误的。然而,如果您正在编写集成测试,那么为什么要在那里使用Mockito,这是没有意义的(正如skaffman所说)!
编辑:现在在您的代码中,您的a直接在before块中设置CustomerServiceImpl
,这也没有意义。那里根本不涉及春天!
@Before
public void setup() {
customService = new CustomServiceImpl();
Setter.set(customService, "dataService", dataService);
}
EDIT 2:如果要编写CustomerServiceImpl
的单元测试,请避免使用Spring内容,并直接注入属性的值。此外,您还可以使用Mockito将DataService
mock straigth注入到测试实例中。
@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只注入mock。
您可以自动连接到赋值函数(setter)中,而不仅仅是注释私有字段。然后,您也可以使用测试类中的setter。无需将其公开,包私有化即可,因为Spring仍然可以访问它,但除此之外,只有您的测试才能进入(或同一包中的其他代码)。
@Value("#{myProp['custom.url']}")
String setUrl( final String url ) {
this.url = url;
}
我不喜欢仅仅为了测试而进行不同的自动布线(与我的代码库相比),但从测试中更改被测试类的替代方案简直是邪恶的。
您不应该嘲笑您试图测试的东西。这是毫无意义的,因为你不会接触任何你试图测试的代码。相反,从上下文中获取CustomerServiceImpl
的实例。
我从Properties文件中读取了一个字符串列表。在@Before块中使用的ReflectionTestUtils类setField方法帮助我在执行测试之前设置了这些值。它甚至适用于我的dao层,它依赖于Common DaoSupport类。
@Before
public void setList() {
List<String> mockedList = new ArrayList<>();
mockedSimList.add("CMS");
mockedSimList.add("SDP");
ReflectionTestUtils.setField(mockedController, "ActualListInController",
mockedList);
}
您可以使用这个小实用程序类(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");
,因为您不必依赖硬编码的字段名。