【问题标题】:Testing lazy initialization by j.u.f.Supplier with Mockitoj.u.f.Supplier 使用 Mockito 测试延迟初始化
【发布时间】:2019-11-08 21:03:23
【问题描述】:

我有一个类Sut 使用java.util.function.Supplier 实现了延迟初始化。事实上它比下面的代码更复杂,但这是 Mockito 无法测试的最简单的形式。下面的测试会引发错误Wanted but not invoked ... However, there were other interactions with this mock。为什么 Mockito 不计算 create 的调用?代码流实际进入create();我用调试器检查过。

import java.util.function.Supplier;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

public class TestTimes {

    @Test
    public void testCreateOnlyOnce() {
        Sut sut = spy(new Sut());
        sut.getData();
        sut.getData();
        sut.getData();
        verify(sut, times(1)).create();
    }

    private static class Sut {
        Supplier<Object> data = this::create;

        void getData() {
            data.get();
        }

        Object create() {
            return new Object();
        }
    }
}

【问题讨论】:

  • 我不知道答案,但我只想说这可能是我见过的mcve 最好的例子之一(最小、完整和可验证的例子)在这个网站上看到。

标签: java mockito supplier


【解决方案1】:

我想补充一点 Gabriel Pimentas 的出色回答。这样做的原因是 mockito 创建了 spy new Sut() 的浅表副本,而您的 Supplier 指的是原始 Sut 实例中编译的 lambda 方法,而不是 spy 实例。

另请参阅 this question 和 the mockito documentation。

调试代码时,您可以看到它是如何工作的:

Sut sut = spy(new Sut()); 现在是Sut 的模拟/间谍子类,作为TestTimes$Sut$MockitoMock$1381634547@5b202a3a 的实例。现在,原始new Sut() 中的所有字段都是浅拷贝的,包括Supplier&lt;Object&gt; data。查看间谍内部的这个字段,我们可以看到它是TestTimes$Sut$$Lambda$1/510109769@1ecee32c 的一个实例,即指向原始Sut 中的一个lambda。当我们在 create 方法中设置断点时,我们可以进一步观察到this 指向TestTimes$Sut@232a7d73,即原来的Sut,而不是被窥探的实例。

编辑:尽管这个 MCVE 可能与您的实际代码不一样,但还是会想到以下解决方案:

  • 将供应商注入您的Sut(在构造期间或作为getData 的参数。
  • 在您的 getData 方法中懒惰地创建供应商(使其指向模拟实例)
  • 不要使用供应商,如果没有从外部传递供应商,则直接调用create

【讨论】:

    【解决方案2】:

    首先,感谢您写得很好。

    我自己测试了你的代码,看到了你提到的错误。虽然,我在调试的时候稍微修改了你的代码……看看:

        @Test
        public void testCreateOnlyOnce() {
            Sut sut = spy(new Sut());
            sut.getData();
            sut.getData();
            sut.getData();
            verify(sut, times(1)).create();
        }
    
        private static class Sut {
    
            private Supplier<Object> data;
    
            // Added de data object initialization on the constructor to help debugging.
            public Sut() {
                this.data = this::create;
            }
    
            void getData() {
                data.get();
            }
    
            Object create() {
                return new Object();
            }
        }
    

    我在调试时发现的:

    1. Sut 类构造函数在spy(new Sut()) 子句中被正确调用,但create() 方法没有被调用。
    2. 每次调用sut.getData(),也会调用create()方法。是什么让我得出结论,最终:

    在构造函数中,this::create 所做的只是告诉 java,每当它需要从供应商处检索 Object 时,将从 create() 方法中检索到 Object。而且,供应商调用的create() 方法来自一个不同于Mockito 正在监视的类实例。

    这就解释了为什么你不能用 verify 来跟踪它。

    编辑:根据我的研究,这实际上是供应商的期望行为。它只是创建一个具有get() 方法的接口,该方法调用您在方法引用中声明的任何 noArgs 方法。查看“使用方法参考实例化供应商”中的 this。

    【讨论】:

      猜你喜欢
      • 2023-04-03
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      • 1970-01-01
      • 2020-10-08
      • 2017-11-11
      相关资源
      最近更新 更多