我已经实现了一个应该进行单元测试的类。注意,这里显示的代码只是一个伪实现,Random
只是为了说明问题。真正的代码将使用真正的依赖项,例如另一个服务或存储库。
public class MyClass {
public String doWork() {
final Random random = new Random(); // the `Random` class will be mocked in the test
return Integer.toString(random.nextInt());
}
}
我想用Mockito模拟其他类,并编写了一个非常简单的JUnit测试。然而,我的班级在测试中没有使用模拟:
public class MyTest {
@Test
public void test() {
Mockito.mock(Random.class);
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// this fails, because the `Random` mock is not used :(
}
}
即使使用MockitoJUnitRunner
(JUnit 4)运行测试,或使用m1n 2o1p进行扩展(JUnit5)并用@Mock
注解也无济于事;实际实现仍在使用:
@ExtendWith(MockitoExtension.class) // JUnit 5
// @RunWith(MockitoJUnitRunner.class) // JUnit 4
public class MyTest {
@Mock
private Random random;
@Test
public void test() {
final MyClass obj = new MyClass();
Assertions.assertEquals("0", obj.doWork()); // JUnit 5
// Assert.assertEquals("0", obj.doWork()); // JUnit 4
// `Random` mock is still not used :((
}
}
为什么没有使用mocked类,即使在测试我的类或使用Mockito扩展/运行程序执行测试之前调用了Mockito方法?
1条答案
按热度按时间9jyewag01#
问题概述(类与示例)
模型是示例(这就是为什么它们也称为“模拟对象”)。对类调用
Mockito.mock
将返回该类的模拟对象。它必须被赋值给一个变量,然后该变量可以传递给相关方法或作为依赖注入到其他类中。它不会不会修改类本身!想想看:如果这是真的,那么类的所有示例都会神奇地转换为mock。这将使得无法模拟使用多个示例的类或来自JDK的类,例如List
或Map
(首先不应该模拟这些类,但这是另一回事)。使用Mockito扩展名/runner的
@Mock
注解也是如此:创建一个模拟对象的新示例,然后将其分配给用@Mock
注解的字段(或参数)。这个模拟对象仍然需要传递给正确的方法或作为依赖注入。另一种避免混淆的方法是:Java中的
new
将“始终”为对象分配内存,并将初始化真实类的这个新示例。无法重写new
的行为。即使像Mockito这样的聪明框架也无法做到这一点。解决方案
»但我怎么能嘲笑我的班级呢?«你会问的。将类的设计更改为可测试的!每次您决定使用
new
时,都会将自己提交给这种类型的示例。根据您的具体用例和要求,存在多种选项,包括但不限于:1.如果可以更改方法的签名/接口,请将(mock)示例作为方法参数传递。这要求示例在所有调用站点中都可用,这可能并不总是可行的。
1.如果无法更改方法的签名,请在构造函数中输入inject the dependency,并将其存储在一个字段中,以便以后方法使用。
1.有时,示例只能在调用方法时创建,而不能在调用之前创建。在这种情况下,可以引入另一个间接级别,并使用abstract factory pattern。然后,工厂对象将创建并返回依赖项的示例。工厂可以存在多个实现:一个返回实际依赖项,另一个返回双重测试,例如模拟。
以下是每个选项的示例实现(有无Mockito runner/extension):
更改方法签名
构造函数依赖注入
工厂延迟施工
根据依赖项的构造函数参数的数量和表达代码的需要,可以使用JDK中的现有接口(
Supplier
、Function
和BiFunction
)或引入自定义工厂接口(如果只有一个方法,则用@FunctionInterface
注解)。下面的代码将选择自定义接口,但与
Supplier<Random>
配合使用效果很好。