繁体   English   中英

mockito 中的 Spring 值注入

[英]Spring value injection in mockito

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

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 中。 我想知道是否有办法完成这项工作。

谢谢

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 模拟。 ContextConfiguration不足以用 spring 连接东西。 从技术上讲,如果您想要与弹簧相关的东西,您可以使用 JUnit 的以下运行程序: SpringJUnit4ClassRunner

此外,在编写单元测试时,您可能需要重新考虑使用 spring。 在单元测试中使用弹簧接线是错误的。 但是,如果您要编写集成测试,那么为什么要在那里使用 Mockito,这没有意义(如 skaffman 所说)!

编辑:现在在你的代码中你直接在你的 before 块中设置CustomerServiceImpl ,这也没有意义。 那里根本不涉及春天!

@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 仅注入模拟。

您可以自动连接到一个 mutator(setter),而不仅仅是注释私有字段。 然后你也可以从你的测试类中使用那个 setter。 无需公开,包私有即可,因为 Spring 仍然可以访问它,否则只有您的测试可以进入(或同一包中的其他代码)。

@Value("#{myProp['custom.url']}")
String setUrl( final String url ) {
    this.url  = url;
}

我不喜欢仅仅为了测试而以不同的方式自动装配(与我的代码库相比),但是从测试中更改被测类的替代方法简直是邪恶的。

我有一个从属性文件中读取的字符串列表。 @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);
}

你不应该嘲笑你试图测试的东西。 这是毫无意义的,因为您不会触及您尝试测试的任何代码。 而是从上下文中获取CustomerServiceImpl的实例。

您可以使用这个小实用程序类 ( 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 实用程序类来解析所有占位符值。 您还可以更改参数的类型properties ,以PlaceholderResolver你喜欢用自己的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"); 因为您不必依赖硬编码的字段名称。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM