使用 Mockito2 在 java 中模拟本地创建的对象



我正在使用testng和Mockito2为一个项目编写模块测试。我想模拟一些发出出站请求的方法。现在,要模拟的对象是在另一个对象的方法中本地创建的。所以,如果我说,4个类,A,B,C和D,使得A创建一个B类型的对象,B创建一个C类型的对象等等,而D类型的对象被模拟,我看到我有两个选项来模拟它。

选项 1 是监视 A、B、C 类型的对象,并将 B 的间谍注入 A 和 C 注入 B,最后在对象创建过程中将 D 的模拟注入 C 中。下面是一个示例。

class A {
public B createB()
{
retrun new B();
}
public void someMethod ()
{
B b = createB();
}
}

通过这种方式,我可以监视 A 并在调用 createB 时为 B 注入模拟对象。这样我最终可以模拟D。

选项 2 是不模拟间歇类,直接有一个工厂类

,如下所示:
class DFactory {
private static D d;
static public void setD (D newD)
{
d = newD;
}
public static D getD()
{
if (d!=null)
{
return d;
} else
{
return new D();
}
}
}

上面的选项很简单,但我不确定这是否是正确的做法,因为它创建了更多的静态方法,我相信应该避免这种情况。

我想知道应该首选哪种方法,以及是否有其他选择。

请注意,我不希望使用powermockito或任何其他鼓励不良代码设计的框架。我想坚持 mockito2。我可以重构我的代码以使其更易于测试。

你现在的方式是,A 创建 B,B 创建 C,C 创建 D,所有这些创建都是你看不到或更改的实现细节,特别是依赖项对象的创建

您令人钦佩地避免使用 PowerMockito,并且您对重构代码以很好地处理此更改也非常感兴趣,这意味着将D 的选择委托给 A 的创建者。虽然我知道你只是想在测试中做出这种选择,但语言并不知道这一点;您正在为依赖项选择不同的实现,并从 C 的实现中获取选择权。这称为控制反转依赖注入。(你可能以前听说过它们,但我在最后介绍这些术语,因为它们通常与权重和框架相关联,而这些框架现在对于本次对话来说并不是真正必要的。

这有点棘手,因为看起来您不仅需要 D 的实现,还需要创建 D 的新实现。这使得事情变得更加困难,但不是很多,特别是如果你能够使用Java 8 lambda和方法引用。在下面的任何地方,您都会看到对D::new的引用,这是对 D 构造函数的方法引用,可以接受为Supplier<D>参数。

我会通过以下方式之一重组您的类:

  • new A()一样构造 A,但将 D 实现的控制留到实际调用 A 时,如aInstance.doSomething(new D())aInstance.doSomething(D::new)。这意味着每次调用方法时,C 都会委托给调用方,从而为调用方提供更多控制权。当然,您可以选择提供内部调用aInstance.doSomething(new D())的重载aInstance.doSomething(),以使默认情况变得容易。
  • 构造 A 类似new A(D::new),其中 A 调用new B(dSupplier),B 调用new C(dSupplier)。这使得在单元测试中替换 B 和 C 变得更加困难,但如果唯一可能的更改是让网络堆栈由 D 表示,那么您只是根据用例的需要更改代码。
  • 构造类似new A(new B(new C(D::new))).这意味着 A 只参与其直接协作者 B,并且更容易将 B 的任何实现替换到 A 的单元测试中。这假设 A 只需要 B 的单个实例而不需要创建它,这可能不是一个好的假设;如果所有类都需要创建其子类的新实例,则 A 将接受Supplier<B>,而 A 的构造将看起来像new A(() -> new B(() -> new C(D::new)))。 这很紧凑,但很复杂,您可以选择创建一个 AFactory 类来管理 A 的创建及其依赖项的配置。

如果第三个选项对你很有吸引力,并且你认为你可能想要自动生成一个像AFactory这样的类,请考虑研究像Guice或Dagger这样的依赖注入框架

最新更新