【问题标题】:How can I pass an object to my class under test in a UnitTest class?如何在 UnitTest 类中将对象传递给我的测试类?
【发布时间】:2017-06-08 22:07:33
【问题描述】:

我想测试一个将“ObjectMapper”作为实例变量的类:

public class EventTransformerImpl implements EventTransformer {


 @Inject
 private ObjectMapper objectMapper;

private String convertParametersToJson(NotificationConfig config) {
        String parametersAsJson = null;
        try {
            parametersAsJson = objectMapper.writeValueAsString(config.getOrdinaryParameters());
        } catch (IOException e) {
            logger.info("There was a problem in writing json:" + config.getParameters(), e);
            parametersAsJson = null;
        }
        return parametersAsJson;
    }
}

这个类没有任何用于初始化“objectMapper”的“setter”或“constructor”(它是使用“spring”初始化的):

<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
    <property name="dateFormat">
        <bean class="java.text.SimpleDateFormat">
            <constructor-arg value="yyyy-MM-dd'T'HH:mm:ssZ"/>
        </bean>
    </property>
</bean>

当我想测试“EventTransformerImpl”类时,objectMapper 的值为空,如何在我的单元测试类中将“objectMapper”传递给“EventTransformerImpl”。

【问题讨论】:

  • 是否有理由不向您正在测试的类添加 setter?提高可测试性会提高可维护性。
  • @David Wallace 我完全同意可测试性。但是 setter 允许可变性。如果不需要依赖的可变性,我宁愿使用构造函数。
  • 是的。构造函数更好,至少在只有一个字段要设置的情况下。
  • @user2688902,如果你得到答案,请养成接受答案的习惯,这样可以鼓励人们将来回答更多问题。它还可以帮助其他人快速找到正确的解决方案。

标签: java spring unit-testing mockito inject


【解决方案1】:

1) 一个类应该是自然可测试的:为什么不在EventTransformerImpl 中添加一个构造函数来注入映射器?

你可以在 spring 配置中做类似的事情:

  <bean id="EventTransformer" class="EventTransformerImpl">
      <constructor-arg ref="objectMapper"/>
   </bean>

在测试中,您可以在构造函数中使用 ObjectMapper 模拟依赖项来实例化 EventTransformerImpl

2)另一种解决方案是在测试方法中使用反射将被测类中的字段注入。

您可以使用 Spring 的 setField() method ofReflectionTestUtils 类来设置依赖关系,或者如果您不使用 Spring,您可以编写一个简单的实用方法来完成这项工作:

public static final void injectValue(String fieldName, Object fieldValue, Object targetObject) {
    try {
        Field f = targetObject.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(targetObject, fieldValue);
    } catch (Exception e) {
       // handle the exception
    }
}

【讨论】:

  • 由于使用了Spring,所以最好使用ReflectionTestUtils,而不是编写自己的方法进行反射。
  • 完全同意。我不记得上课了。谢谢你:)
【解决方案2】:

为了在 JUnit 测试和它们依赖的其他对象中注入 Spring 依赖项,我使用

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:testBeans.xml" })
public class TestRunner {

    @Test
    public void test1() {

您也可以提供代码内 bean 初始化

@RunWith(SpringJUnit4ClassRunner.class)
@Configuration

阅读更多https://www.mkyong.com/unittest/junit-spring-integration-example/

【讨论】:

  • 尽管已经给出了这个答案的其他问题的答案很多;我仍然赞成;因为你是第一个指出 Spring 有办法解决这个问题的人。
【解决方案3】:

这是一个典型的例子,一个模拟框架,例如Mockito,可以帮助你:

public class TestClass {
  @Mock
  private ObjectMapper objectMapper;

  @InjectMocks
  private EventTransformer eventTransformer;

  @BeforeMethod
  public void setUp() {
    eventTransformer = new EventTransformerImpl();
    MockitoAnnotations.initMocks(this);
  }  
}

这里,@Mock@InjectMocks 是 Mockito 框架的一部分。魔术发生在MockitoAnnotations.initMocks(this) 中,它将扫描this,在本例中为TestClass,以获取这些注释。 Mockito 将objectMapper 初始化为模拟,并将其注入eventTransformer。然后,您可以使用 Mockito 来决定 objectMapper 的行为方式。

您可以阅读有关 Mockito here 的更多信息。

另外,@BeforeMethod 是一种 TestNG 方法,类似于 JUnits @Before


但是,很多人更喜欢constructor injection,正如davidxxx 所建议的那样。这将使EventTransformerImpl 具有哪些依赖项更加清晰,并强制使用正确的依赖项对其进行初始化。通过这样做,不需要“神奇地”(我猜 Mockito 在后台使用反射)注入依赖项,只需通过调用构造函数初始化要测试的类。

你的类可能看起来像这样(这里使用 Spring 的 @Autowired 注释):

public class EventTransformerImpl implements EventTransformer {

  private ObjectMapper objectMapper;

  @Autowired
  public EventTransformerImpl(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
  }

  private String convertParametersToJson(NotificationConfig config) {
    String parametersAsJson = null;
    try {
      parametersAsJson = objectMapper.writeValueAsString(config.getOrdinaryParameters());
    } catch (IOException e) {
      logger.info("There was a problem in writing json:" + config.getParameters(), e);
      parametersAsJson = null;
    }
    return parametersAsJson;
  }
}

这将不太容易出错,因为使用@InjectMocks,如果注入失败,Mockito 将默默地忽略注入,同时调用构造函数让测试类完全控制测试的初始化。

【讨论】:

  • 该解决方案有效并且减少了编写代码。我们可以认为是一件好事。但我个人不使用它,因为它有一些重要的缺点。 1) 它提倡不透明的设计。当您查看测试代码时,您没有明确显示如何设置依赖项的设置方法 2) 它促进了一种不自然的方式来编写、阅读和理解测试。如果您练习 TDD 或依赖测试作为主导进行开发,您将永远不会使用它 3) 它容易出错,因为如果注入失败,Mockito 不会报告失败。
  • @davidxxx 这正是我在答案中添加最后一部分的原因。使用 Spring 支持的构造函数注入。这将“修复”第 1 点和第 3 点。只需删除 @InjectMocks 注释并使用构造函数创建对象。我真的不明白你说的第2点是什么意思?你觉得嘲笑objectMapper是件坏事吗?
  • 毫无疑问,您已经意识到了这个问题并且您知道解决方案,但我会给出我不使用这种 Mockito 机制的所有个人原因。对于第 2 点,想法是当您以简单的方式或在 TDD 中编写测试时,您编写的内容在设置中看起来很自然且可以直接理解。例如:类似:EventTransformer transformerToTest = new EventTransformer(objectMapperMock);eventTransformer = new EventTransformerImpl(); MockitoAnnotations.initMocks(this); 不太自然和易于理解。
  • @davidxxx 是的,我完全同意这一点。我不知何故感觉到你在反对objectMapper的嘲笑。我可能会扩展我的答案的构造函数注入部分。
  • 我没有回答抱歉,但你是对的,在单元测试中模拟依赖是一件好事。如您所愿,您的答案已经很好了:)
【解决方案4】:

三个答案都不错;所以我不需要重复那些细节,但我认为值得在“完整”图片上“对齐”。

如图所示,Spring 内置了为单元测试进行注入的方法;你可以继续使用它。但当然,这意味着您将业务逻辑代码更多地“耦合”到 Spring 框架。

因此,如果您曾经考虑在非 Spring 的情况下重用您的代码,那么您会发现不仅您的生产代码,而且您的单元测试都需要进行重大修改才能在没有 Spring 的情况下使用。

因此,建议更改您的生产代码以使您至少完全测试您的生产代码而无需在此处添加对 Spring 的依赖项的另外两个答案具有一定的优点。

【讨论】:

  • 我无法更改 Spring 配置或原始类...我只想编辑测试类....谢谢(:
猜你喜欢
  • 2016-10-07
  • 1970-01-01
  • 2012-07-20
  • 1970-01-01
  • 2014-11-28
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 1970-01-01
相关资源
最近更新 更多