我有以下情况:我的类保留了两个对象的标识集。
集合逐渐填充了一些实例,在测试用例中,它们填充了mock。
然后,我的类有一个调用,它对一个集合的内容执行某些操作,然后再对另一集合的内容进行操作。这就是我正在测试的呼叫。
处理集合的顺序很重要。在集合中处理单个objecst的顺序并不重要,事实上,它取决于如何将身份哈希分配给集合中的mock。
所以在我的情况下,我有一个偏序:所有这些对象都必须在任何一个对象之前处理。
问题是,我如何用Mockito来表达它?inOrder()会崩溃,因为我必须指定对象的确切顺序,而这不是恒定的。
为了讨论在使用Mockito.inOrder时如何处理集合和地图,我们必须根据区分4种情况
- 集合或映射是输入的还是在方法内部创建的(输入意味着它是通过方法参数、类变量访问的,或者是通过可以存根的方法调用返回的)
- 集合或映射是否在方法内部被访问或迭代(或来自同一类的另一个方法或被调用的超类,因此无法存根)
这导致了4种组合或4种可能的情况。
-
集合或映射是正在测试的方法的输入参数,该方法将其传递给另一个方法或返回它,但不接触它。在这种情况下,只需模拟集合或映射即可。没有问题。
-
集合或映射是正在测试的方法的输入参数,该方法访问(或迭代)它。在这种情况下,您将创建一个包含一个或多个mock的集合或集合。在这种情况下,您永远不应该模拟集合或映射本身,因为在使用for循环或流对其进行迭代时,您不知道调用了哪些方法,并且需要对其进行存根处理(这取决于JRE的实现!)。因为在调用方法之前必须自己创建它,所以您可以决定要使用什么实现。如果它包含2个或多个模拟对象,或者正在测试的方法向其中添加对象,则创建有序集合或映射。如果该方法不应该修改它,而只应该读取它,那么创建一个不可修改的集合或映射。我喜欢在测试中使用Collections.singleton、Collections.singletonList和Arrays.asList。
-
被测试的方法创建并填充集合,但不访问或迭代它。如果该方法将对象添加到集合或映射中,这些对象作为参数传入,然后将集合或映射传递给另一个映射或返回,则没有问题。在这种情况下,不存在依赖于迭代顺序的要验证的调用。您只需要在之后断言它的内容(当返回时,或者如果没有返回但传递给另一个方法,则使用captor)。
-
正在测试的方法创建并填充集合,并访问或迭代它。当该方法创建集合或映射并将mock添加到其中,然后对其进行迭代并对这些mock执行操作时,如果您使用inOrder并且集合或映射未排序,则确实存在问题。在这种情况下,您应该尝试将该方法拆分为两个方法:一个方法创建并填充集合或映射(在情况3中结束,它可以是工厂类中的工厂方法),另一个方法访问或迭代它(在情况2中结束)。只需创建一个工厂方法并从现有方法中调用它就足以解决问题。
当包含正在测试的方法的类具有作为类变量的集合或映射时,如果方法访问或修改它,则可以将其视为方法的输入;如果方法设置了它,则将其视作为输出。但是,如果它是一个没有setter或getter的私有或受保护类变量,那么在调用方法之前,您可能需要使用反射用mock或包含一个或多个mock的集合或映射来设置它,或者在调用方法之后获取并验证它。