mockito中的弹簧值注入



我正在尝试为以下方法编写测试类

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。在测试中,您可以使用它注入一组作为MapProperties实例给定的值,如以下示例所示:

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");,因为您不必依赖硬编码的字段名。

相关内容

  • 没有找到相关文章

最新更新