我在这里读了几个关于静态方法的线程,我想我明白了误用/过度使用静态方法会导致的问题。但我并没有真正了解为什么很难模拟静态方法。
我知道其他嘲笑框架,比如PowerMock,可以做到这一点,但为什么不能Mockito?
我读过this article,但作者似乎在虔诚地反对static
这个词,也许这是我的理解不足。
简单的解释/链接会很棒。
答案 0 :(得分:212)
我认为原因可能是模拟对象库通常通过在运行时动态创建类来创建模拟(使用cglib)。这意味着他们要么在运行时实现一个接口(如果我没有弄错的话就是EasyMock所做的那样),或者他们从类继承到mock(如果我没弄错的话,这就是Mockito所做的)。这两种方法都不适用于静态成员,因为您无法使用继承覆盖它们。
模拟静态的唯一方法是在运行时修改类的字节代码,我认为这比继承要多一些。
这是我对它的猜测,因为它的价值......
答案 1 :(得分:28)
如果你需要模拟静态方法,它是一个糟糕设计的强大指标。通常,您可以模拟被测试类的依赖关系。如果你的被测试类是指一个静态方法 - 例如java.util.Math#sin--这意味着被测试类正好需要这个实现(例如精度与速度)。如果你想从一个具体的正弦实现中抽象出来,你可能需要一个接口(你知道这会发生什么)?
答案 2 :(得分:7)
Mockito [3.4.0] can mock static methods!
带有静态方法的类:
class Buddy {
static String name() {
return "John";
}
}
使用新方法Mockito.mockStatic()
:
@Test
void lookMomICanMockStaticMethods() {
assertThat(Buddy.name()).isEqualTo("John");
try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) {
theMock.when(Buddy::name).thenReturn("Rafael");
assertThat(Buddy.name()).isEqualTo("Rafael");
}
assertThat(Buddy.name()).isEqualTo("John");
}
Mockito仅替换try
块中的静态方法。
答案 3 :(得分:6)
如果你需要模拟静态方法,我认真地认为它是代码味道。
这对我来说似乎有点过分了,就像Guava这样的库,但你不应该嘲笑这种类型,因为它是逻辑的一部分......(像Iterables.transform(..)这样的东西) /> 这样你自己的代码就会保持干净,你可以用干净的方式模拟所有的依赖项,并且你有一个针对外部依赖的反腐败层。 我在实践中看到了PowerMock,我们需要的所有类都设计得很糟糕。此外,PowerMock的集成有时会导致严重的问题(例如https://code.google.com/p/powermock/issues/detail?id=355)
PS:同样适用于私有方法。我认为测试不应该知道私有方法的细节。如果一个类是如此复杂以至于它试图模仿私有方法,那么它可能是分裂该类的一个标志......
答案 4 :(得分:4)
Mockito返回对象,但静态意味着“类级别,而不是对象级别”因此mockito将为静态提供空指针异常。
答案 5 :(得分:1)
在某些情况下,静态方法可能难以测试,特别是如果需要进行模拟,这就是大多数模拟框架不支持它们的原因。我发现this博客文章对于确定如何模拟静态方法和类非常有用。
答案 6 :(得分:1)
作为Gerold Broser's answer的补充,下面是一个使用参数模拟静态方法的示例:
class Buddy {
static String addHello(String name) {
return "Hello " + name;
}
}
...
@Test
void testMockStaticMethods() {
assertThat(Buddy.addHello("John")).isEqualTo("Hello John");
try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) {
theMock.when(() -> Buddy.addHello("John")).thenReturn("Guten Tag John");
assertThat(Buddy.addHello("John")).isEqualTo("Guten Tag John");
}
assertThat(Buddy.addHello("John")).isEqualTo("Hello John");
}