【问题标题】:How to inject mocks while testing classes using CDI in production如何在生产中使用 CDI 测试类时注入模拟
【发布时间】:2015-12-02 01:55:51
【问题描述】:

我正在使用 WELD-SE 进行依赖注入的 Java SE 环境中进行编程。因此一个类的依赖看起来像这样:

public class ProductionCodeClass {
    @Inject
    private DependencyClass dependency;
}

在为此类编写单元测试时,我正在为 DependencyClass 创建一个模拟,并且由于我不想为我运行的每个测试启动一个完整的 CDI 环境,我手动“注入”模拟:

import static TestSupport.setField;
import static org.mockito.Mockito.*;

public class ProductionCodeClassTest {
    @Before
    public void setUp() {
        mockedDependency = mock(DependencyClass.class);
        testedInstance = new ProductionCodeClass();
        setField(testedInstance, "dependency", mockedDependency);
    }
}

静态导入的方法setField()我用我在测试中使用的工具在一个类中编写了自己:

public class TestSupport {
    public static void setField(
                                final Object instance,
                                final String field,
                                final Object value) {
        try {
            for (Class classIterator = instance.getClass();
                 classIterator != null;
                 classIterator = classIterator.getSuperclass()) {
                try {
                    final Field declaredField =
                                classIterator.getDeclaredField(field);
                    declaredField.setAccessible(true);
                    declaredField.set(instance, value);
                    return;
                } catch (final NoSuchFieldException nsfe) {
                    // ignored, we'll try the parent
                }
            }

            throw new NoSuchFieldException(
                      String.format(
                          "Field '%s' not found in %s",
                          field,
                          instance));
        } catch (final RuntimeException re) {
            throw re;
        } catch (final Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

我不喜欢这个解决方案的一点是,我在任何新项目中都需要这个助手。我已经将它打包为一个 Maven 项目,我可以将其作为测试依赖项添加到我的项目中。

但是在我缺少的其他一些公共库中没有现成的东西吗?一般情况下,我有什么方法可以做到这一点?

【问题讨论】:

  • 使用构造函数注入。

标签: java unit-testing mockito cdi


【解决方案1】:

Mockito 开箱即用地支持这一点:

public class ProductionCodeClassTest {

    @Mock
    private DependencyClass dependency;

    @InjectMocks
    private ProductionCodeClass testedInstance;

    @Before
    public void setUp() {
        testedInstance = new ProductionCodeClass();
        MockitoAnnotations.initMocks(this);
    }

}

@InjectMocks 注解将触发注入在测试类中模拟的类或接口,在本例中为DependencyClass

Mockito 尝试按类型注入(在类型相同的情况下使用名称)。当注入失败时,Mockito 不会抛出任何东西——你必须手动满足依赖关系。

这里,我也是使用@Mock注解,而不是调用mock()。你仍然可以使用mock(),但我更喜欢使用注解。

附带说明,有可用的反射工具,它支持您在TestSupport 中实现的功能。一个这样的例子是ReflectionTestUtils


也许更好的是使用constructor injection

public class ProductionCodeClass {

    private final DependencyClass dependency;

    @Inject
    public ProductionCodeClass(DependencyClass dependency) {
        this.dependency = dependency;
    }
}

这里的主要优点是清楚该类所依赖的类,并且如果不提供所有依赖项就无法轻松构建它。此外,它允许注入的类是最终的。

通过这样做,@InjectMocks 不是必需的。相反,只需通过将模拟作为参数提供给构造函数来创建类:

public class ProductionCodeClassTest {

    @Mock
    private DependencyClass dependency;

    private ProductionCodeClass testedInstance;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        testedInstance = new ProductionCodeClass(dependency);
    }

}

【讨论】:

  • 是的,这实际上就是我锁定的目的。已经尝试过了,结果看起来非常好。我找不到这个...... ;-) 我没有使用 Spring,所以只是添加它来获取 ReflectionTestUtils 可能有点重。不过也谢谢你的这个建议。
  • @Magnilex :您是否考虑过在代码中添加@RunWith(MockitoJUnitRunner.class)@Rule public MockitoRule rule ... 以使其完整?
  • @Henrik 代码已完成。 @RunWith(MockitoJUnitRunner.class) 将是一个替代方案。然而,这个答案解释了 OP:s 的问题,这个问题不是在询问如何运行基于 mockito 的 JUnit 测试的替代方案。
  • @Magnilex:啊,好的。我认为必须更明确地调用 Mockito。感谢您的回复。
【解决方案2】:

当 mockitos 内置函数不够用时的替代方案:试试 needle4j.org

它是一个注入/模拟框架,允许注入模拟和具体实例,还支持用于生命周期模拟的 postConstruct。

 public class ProductionCodeClassTest {

    @Rule
    public final NeedleRule needle = new NeedleRule();

    // will create productionCodeClass and inject mocks by default
    @ObjectUnderTest(postConstruct=true)
    private ProductionCodeClass testedInstance;

    // this will automatically be a mock
    @Inject
    private AServiceProductionCodeClassDependsOn serviceMock;

    // this will be injected into ObjectUnderTest 
    @InjectIntoMany
    private ThisIsAnotherDependencyOfProdcutionCodeClass realObject = new ThisIsAnotherDependencyOfProdcutionCodeClass ();

    @Test
    public void test_stuff() {
         ....
    }

}

【讨论】:

猜你喜欢
  • 2018-06-23
  • 2017-02-09
  • 2013-09-17
  • 2015-11-15
  • 2013-09-16
  • 1970-01-01
  • 2014-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多