如何在生产中使用 CDI 测试 类 时注入模拟
How to inject mocks while testing classes using CDI in production
我正在 Java SE 环境中使用 WELD-SE 进行依赖注入编程。因此 class 的依赖项看起来像这样:
public class ProductionCodeClass {
@Inject
private DependencyClass dependency;
}
为此 class 编写单元测试时,我正在为 DependencyClass
创建一个模拟,因为我不想为每个测试启动一个完整的 CDI 环境,我 运行 ,我 "inject" 手动模拟:
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()
我用测试用的工具class自己写的:
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 项目,我可以将其作为测试依赖项添加到我的项目中。
但是我缺少的其他一些公共库中是否有现成的东西?对我的一般做法有何评论?
Mockito 支持开箱即用:
public class ProductionCodeClassTest {
@Mock
private DependencyClass dependency;
@InjectMocks
private ProductionCodeClass testedInstance;
@Before
public void setUp() {
testedInstance = new ProductionCodeClass();
MockitoAnnotations.initMocks(this);
}
}
@InjectMocks
注释将触发注入 classes 或在测试 class 中模拟的接口,在这种情况下 DependencyClass
:
Mockito tries to inject by type (using name in case types are the same). Mockito does not throw anything when injection fails - you will have to satisfy the dependencies manually.
这里,我也使用了@Mock
注解,而不是调用mock()
。您仍然可以使用 mock()
,但我更喜欢使用注释。
附带说明一下,有可用的反射工具,它支持您在 TestSupport
中实现的功能。一个这样的例子是 ReflectionTestUtils
.
也许更好的方法是使用 constructor injection:
public class ProductionCodeClass {
private final DependencyClass dependency;
@Inject
public ProductionCodeClass(DependencyClass dependency) {
this.dependency = dependency;
}
}
这里的主要优点是很清楚 classes class 依赖什么,并且如果不提供所有依赖项就无法轻易构建它。此外,它允许注入的 class 是最终的。
这样做,@InjectMocks
就没有必要了。相反,只需通过将模拟作为参数提供给构造函数来创建 class:
public class ProductionCodeClassTest {
@Mock
private DependencyClass dependency;
private ProductionCodeClass testedInstance;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
testedInstance = new ProductionCodeClass(dependency);
}
}
当 mockitos 内置函数不够用时的替代方法:尝试 needle4j.org
这是一个 injection/mock 框架,允许注入模拟和具体实例,还支持用于生命周期模拟的 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() {
....
}
}
我正在 Java SE 环境中使用 WELD-SE 进行依赖注入编程。因此 class 的依赖项看起来像这样:
public class ProductionCodeClass {
@Inject
private DependencyClass dependency;
}
为此 class 编写单元测试时,我正在为 DependencyClass
创建一个模拟,因为我不想为每个测试启动一个完整的 CDI 环境,我 运行 ,我 "inject" 手动模拟:
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()
我用测试用的工具class自己写的:
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 项目,我可以将其作为测试依赖项添加到我的项目中。
但是我缺少的其他一些公共库中是否有现成的东西?对我的一般做法有何评论?
Mockito 支持开箱即用:
public class ProductionCodeClassTest {
@Mock
private DependencyClass dependency;
@InjectMocks
private ProductionCodeClass testedInstance;
@Before
public void setUp() {
testedInstance = new ProductionCodeClass();
MockitoAnnotations.initMocks(this);
}
}
@InjectMocks
注释将触发注入 classes 或在测试 class 中模拟的接口,在这种情况下 DependencyClass
:
Mockito tries to inject by type (using name in case types are the same). Mockito does not throw anything when injection fails - you will have to satisfy the dependencies manually.
这里,我也使用了@Mock
注解,而不是调用mock()
。您仍然可以使用 mock()
,但我更喜欢使用注释。
附带说明一下,有可用的反射工具,它支持您在 TestSupport
中实现的功能。一个这样的例子是 ReflectionTestUtils
.
也许更好的方法是使用 constructor injection:
public class ProductionCodeClass {
private final DependencyClass dependency;
@Inject
public ProductionCodeClass(DependencyClass dependency) {
this.dependency = dependency;
}
}
这里的主要优点是很清楚 classes class 依赖什么,并且如果不提供所有依赖项就无法轻易构建它。此外,它允许注入的 class 是最终的。
这样做,@InjectMocks
就没有必要了。相反,只需通过将模拟作为参数提供给构造函数来创建 class:
public class ProductionCodeClassTest {
@Mock
private DependencyClass dependency;
private ProductionCodeClass testedInstance;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
testedInstance = new ProductionCodeClass(dependency);
}
}
当 mockitos 内置函数不够用时的替代方法:尝试 needle4j.org
这是一个 injection/mock 框架,允许注入模拟和具体实例,还支持用于生命周期模拟的 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() {
....
}
}