Mockito模拟有多少模拟?

时间:2018-10-05 23:24:56

标签: android unit-testing kotlin mockito

我保证,这是一个严重的问题。我花了最后两个小时阅读了我能找到的尽可能多的Mock定义,没有一个向我解释。

我有一个要测试的类,该类需要一个mapper类作为其主要构造函数的一部分:

mike@mike-Inspiron-3543:~/Documents/programming/useful/extractText$ python -V
Python 3.6.0 :: Anaconda custom (64-bit)

在单元测试中,我有以下代码:

open class PoiListViewModel @Inject constructor(
        private val mapper: PoiMapper
) : ViewModel() {

我对所有聪明的开发人员的问题是,模拟到底是什么?具体来说,它可以复制多少原始班级?

我告诉你我的假设定义。模拟是伪造的替代类,它代表我的真实类,但除了跟踪向其发送的方法调用之外,它什么都不做。如果我希望模拟具有任何功能,我需要在其中添加该功能。

至少那是我对模拟的无知观点。但是我显然很生气,因为在单元测试中,我的“模拟”映射器类似乎是一个 actual 映射器类。如果调试单元测试,我会看到它遍历了我的mapper类的所有代码。我看到它返回转换后的数据。

这是映射器类代码(如果有关系的话):

//Mock objects needed to instantiate the class under test
@Mock lateinit var mapper: PoiMapper

// Class being tested
lateinit var poiListViewModel: PoiListViewModel

@Before
fun setup() {
    MockitoAnnotations.initMocks(this)
    poiListViewModel = PoiListViewModel(mapper)
}

有人可以向我解释一下,一个Mockito模拟是多少模拟?我是否没有正确实例化模拟?有人可以给我一个更好的方式来考虑模拟,以便我可以将所有这些都包裹住吗?

2 个答案:

答案 0 :(得分:1)

您对模拟的理解是正确的。作为Mockito的实现细节,您正在碰到Kotlin的默认默认行为。

Mockito模拟(不同于Mockito间谍)被定义为代替您的类实例。除非您将它们存根否则返回它们,否则它们会记录所有交互并返回默认的伪值(零,空字符串,空列表,空值等)。您可以通过存根返回值(when / thenReturn),存根方法的特定行为(when / thenAnswer)来确认它们与被测系统正确协作。 ,或检查是否已调用某些方法(verify并通过(ArgumentCaptor)检索它们被调用的特定实例。

内幕,Mockito通过生成您要模拟的类的子类,并重写所有委托给Mockito内部处理程序的方法来实现此目的。这就是赋予Mockito静默覆盖行为的能力的原因:被测系统认为它正在调用您的依赖关系,但是您的代码正在使用Java的虚拟方法分派来覆盖依赖关系的行为。

这就是窍门:Java方法默认情况下是虚拟的,除非您将它们标记为final。在Kotlin中,函数为closed by default, unless you mark them open。 (我将继续称它们为final,因为这是JVM或Android Dexer中的定义,该定义正在读取Kotlin生成的字节码。)当虚拟机确定引用基于类型时在静态类型上,并且您正在调用final方法时,允许编译器执行inline that implementation's code (JLS 8.4.3.3),因为您断言该方法不能被覆盖,并且任何试图覆盖该方法的代码都将在编译时失败。 Mockito生成的代码不是用这种方式编译的,Mockito无法检测到这种情况。

在您的情况下,mapToPresentation不是open,因此编译器将其视为final,并且不会保留使Mockito覆盖行为的虚拟方法分派。您对模拟的定义是正确的,您的代码也将是正确的,除了Kotlin是默认关闭的,而Java是默认打开的。此外,您确实确实依赖于函数open,因为您希望使用与函数中的实现不同的实现来调用它。

平时,您可以只设置要覆盖的所有功能open,也可以使用Mockito 2.1+'s built-in feature to mock final methods。但是,作为一般的最佳实践,您希望对象的行为类似于PoiMapper,即使它没有遵循特定的PoiMapper实现。这可能是接口/ Impl拆分的一个很好的理由,例如您的PoiListViewModel类采用了PoiMapper接口。在生产中,您可以按需提供PoiMapperImpl,但是Mockito可以生成PoiMapper接口的任意实现,而不必担心PoiMapperImpl及其功能是打开还是关闭。

答案 1 :(得分:0)

您添加了注释

@RunWith(MockitoJUnitRunner.class)

去你的考试班吗?