【问题标题】:Mockito mock a class that has irrelevant static methodsMockito 模拟具有无关静态方法的类
【发布时间】:2020-01-21 11:35:54
【问题描述】:

我想用具有一些不相关静态方法的类的 Mockito 来模拟一个对象。

这里有很多关于堆栈溢出的问题,说明使用 Mockito 模拟静态方法是不可能的。但是,我在单元测试中需要的对象的静态方法与测试无关。

更具体地说,我想为在缓存中查找文档并在缓存未命中的情况下从 Couchbase lite 数据库加载它们的方法编写单元测试。不幸的是 com.couchbase.lite.Document 类有一些静态方法并试图模拟它们

Document mockDocument = Mockito.mock(Document.class);

产生java.lang.UnsatisfiedLinkError。 我打算模拟一些非静态方法,例如

doReturn("SomeString").when(mockDocument).getString("someKey");

但从不使用静态方法,无论是在测试方法中还是在单元测试本身中。 关于 Couchbase,我想该库与我的问题并不特别相关,只是我想模拟某个库类的对象,其中包含不相关的静态方法和相关的非静态方法。

更新: 这是堆栈跟踪

java.lang.UnsatisfiedLinkError: com.couchbase.lite.internal.core.C4Log.setLevel(Ljava/lang/String;I)V

at com.couchbase.lite.internal.core.C4Log.setLevel(Native Method)
at com.couchbase.lite.FileLogger.setupDomainObjects(FileLogger.java:84)
at com.couchbase.lite.FileLogger.<init>(FileLogger.java:47)
at com.couchbase.lite.Log.<init>(Log.java:35)
at com.couchbase.lite.AbstractDatabase.<clinit>(AbstractDatabase.java:80)
at com.couchbase.lite.internal.support.Log.sendToLoggers(Log.java:401)
at com.couchbase.lite.internal.support.Log.e(Log.java:247)
at com.couchbase.lite.NativeLibraryLoader.load(NativeLibraryLoader.java:41)
at com.couchbase.lite.Document.<clinit>(Document.java:42)
at sun.reflect.GeneratedSerializationConstructorAccessor10.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
at org.mockito.internal.creation.instance.ObjenesisInstantiator.newInstance(ObjenesisInstantiator.java:19)
at org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker.createMock(SubclassByteBuddyMockMaker.java:47)
at org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker.createMock(ByteBuddyMockMaker.java:25)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:63)
at org.mockito.Mockito.mock(Mockito.java:1910)
at org.mockito.Mockito.mock(Mockito.java:1819)
at com.my.app.ClassOfTheTest.nameOfTheTest(ClassOfTheTest.java:1234)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)

【问题讨论】:

  • 你能添加完整的堆栈跟踪吗?我怀疑其中提到了 mockito 未能模拟该类的原因。
  • @second 我更新了我的答案,从我对堆栈溢出的搜索中我认为问题是couchbase.lite.Documents 使用了 Mockito 无法模拟的静态方法。
  • 我用couchbase-lite-java-core 1.4.4 试过这个,但没有收到这个错误。但我注意到我尝试模拟的 Document 类没有 getString(...) 方法,因此您可能使用的是不同的版本。 UnstatisfiedLinkError 通常表示类路径中缺少某些 jar。
  • 我使用的是 Couchbase lite 版本 2.6,我也可以使用旧的 Couchbase lite 版本 (1.4.x) 模拟 couchbase.lite.Document 类,但是新版本 2.x+ 引入了问题.如果我的具体问题不是源于不可修改的静态方法(与我在问题中提出的相反),我很乐意为此找到解决方案,即使这将是另一个问题的答案。
  • mvn 存储库没有 2.6er 版本的 jar,所以我无法验证这一点。但是2.7er community version 没有问题。关于你的问题:我不太明白你想用这些静态方法做什么,因为你提到你从不使用它们。对于一个类的模拟,是否有静态方法应该是无关紧要的。

标签: java android unit-testing mockito


【解决方案1】:

为这些静态方法创建一个 DocumentUtils 类,这样如果您想在 UnitTest 中使用这些方法,您仍然可以在不模拟的情况下这样做。

DocumentUtils.someMethod(args);

更新:

DocumentUtils 类的外观如何(添加了一些可能与您的用例无关的虚构静态方法)

public final class DocumentUtils {

public static boolean isDocumentReadable(Document doc) {
    ...
}

public static boolean isDocumentWrittenInEnglish(Document doc) {
    ...
}

public static List<Document> getEnglishWrittenDocuments(List<Document> docs) {
    ...
}

public static boolean areDocumentsTheSame(Document doc1, Document doc2) {
    ...
}

}

测试:

当涉及到测试时,您甚至可能根本不需要引用这个类...

但是,您需要确保以一种或另一种方式传递给这些方法的模拟 Document 对象将返回预期值。

例如,如果要在某个阶段调用DocumentUtils.isDocumentWrittenInEnglish(Document doc),则必须预先设置模拟以返回预期值:

when(mockedDocument.getLanguage()).thenReturn(LANGUAGE_ENGLISH);

【讨论】:

  • utils 类不会扩展 Document,它的目的是能够完成可重用逻辑并返回答案,而不需要除了传递的对象之外的任何其他依赖项。我已经更新了我的答案。
  • 感谢您的澄清!所以基本上你建议将静态方法移动到一个单独的类(即 DocumentUtils),以便定期模拟剩余的非静态对象,对吧?我现在的问题是我要模拟的“文档”对象来自一个库(即 Couchbase lite),并且静态方法是在这个库中实现的。因此,拆分课程并不像我自己编写课程那样容易。
  • 那么我建议创建一个 DocumentWrapper 类来解决这个问题。 stackoverflow.com/questions/889160/what-is-a-wrapper-class
  • 暗示包装类必须在生产代码中使用而不是原始 Document 类?所以使用包装器而不是直接访问会牺牲提高可测试性,对吧?
  • 正确。包装是一种解决方案,另一种可能是使用 PowerMock,但是,我以前没有使用过它,因此无法提供更多信息,对于应该可以通过正确架构解决的问题,似乎需要做很多工作,但是在您的场景中可能值得看看...github.com/powermock/powermock/wiki/…
猜你喜欢
  • 2022-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-30
  • 2014-02-02
相关资源
最近更新 更多