如何在生产中使用CDI测试类时注入模拟对象?

20

我正在使用WELD-SE实现Java SE环境下的依赖注入编程。因此,类的依赖关系看起来像这样:

public class ProductionCodeClass {
    @Inject
    private DependencyClass dependency;
}

在为这个类编写单元测试时,我正在为DependencyClass创建mock,并且因为我不想为每次运行测试都启动完整的CDI环境,所以我手动“注入”了mock:

当为该类编写单元测试时,我创建一个 DependencyClass 的mock对象,并且由于我不想为每一次运行测试都启动一个完整的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 项目,可以将其作为测试依赖项添加到我的项目中。

但是,在其他常用库中是否有现成的东西我错过了吗?对我通常的做法有什么评论吗?


1
使用构造函数注入。 - chrylis -cautiouslyoptimistic-
2个回答

28

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


更好的方法是使用构造函数注入

public class ProductionCodeClass {

    private final DependencyClass dependency;

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

这里的主要优点是清楚地了解该类所依赖的类,并且如果没有提供所有依赖项,则无法轻松构造它。此外,它允许注入的类为final。

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

public class ProductionCodeClassTest {

    @Mock
    private DependencyClass dependency;

    private ProductionCodeClass testedInstance;

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

}

1
是的,那正是我在寻找的。已经尝试过它,结果看起来非常不错。我之前一直找不到这个...;-) 因此,我没有使用Spring,只是为了获取ReflectionTestUtils而添加它可能会有点沉重。但是无论如何,还是谢谢您的建议。 - Matthias Wimmer
@Magnilex:你是否考虑过在你的代码中添加 @RunWith(MockitoJUnitRunner.class) 或者 @Rule public MockitoRule rule ... 来使其更完整? - Henrik Aasted Sørensen
@Henrik,代码已经完成。不过,@RunWith(MockitoJUnitRunner.class)也是一种替代方案。然而,这个答案解释了OP的问题,问题并没有要求提供如何运行基于Mockito的JUnit测试的替代方案。 - Magnilex
@Magnilex:啊,好的。我以为Mockito需要更明确地调用。谢谢回复。 - Henrik Aasted Sørensen
对于 Junit5,你可以使用注解 @ExtendWith(MockitoExtension.class) 来替代 @RunWith(MockitoJUnitRunner.class)MockitoExtension 可在 Maven 依赖中找到:org.mockito:mockito-junit-jupiter - Marteng

2
当mockito的内置函数不够用时,可以尝试使用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() {
         ....
    }

}

很好!不知道是否有示例项目/测试套件?类似JMockit的示例(https://github.com/jmockit/jmockit1/tree/master/samples/petclinic)将是理想的。 - Rogério
嗯,目前有一些基于2.2的示例,但我们尚未将它们迁移到2.3版本。不过这个链接可能会有帮助:https://github.com/needle4j/needle/tree/master/src/examples - Jan Galinski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接