Тестирование отложенной инициализации с помощью jufSupplier с Mockito - PullRequest
3 голосов
/ 27 июня 2019

У меня есть класс Sut с ленивой инициализацией, реализованной с использованием java.util.function.Supplier. На самом деле это сложнее, чем приведенный ниже код, но это самая простая форма, которую Mockito не может протестировать. Тест ниже выдает ошибку Wanted but not invoked ... However, there were other interactions with this mock. Почему Мокито не считает вызов 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();
        }
    }
}

Ответы [ 2 ]

3 голосов
/ 27 июня 2019

Хочу немного добавить к Габриэлю Пиментасу отличный ответ. Причина, по которой это работает так, как работает, состоит в том, что mockito создает мелкие копии шпиона new Sut(), а ваш Supplier ссылается на скомпилированный лямбда-метод внутри исходного экземпляра Sut, а не на экземпляр шпиона.

См. Также этот вопрос и документация по мокито .

Когда вы отлаживаете свой код, вы можете увидеть, как это работает:

Sut sut = spy(new Sut()); теперь является поддельным / шпионским подклассом Sut в качестве экземпляра TestTimes$Sut$MockitoMock$1381634547@5b202a3a. Теперь все поля из оригинального new Sut() копируются на мелкие участки, включая Supplier<Object> data. Глядя на это поле внутри шпиона, мы видим, что это экземпляр TestTimes$Sut$$Lambda$1/510109769@1ecee32c, то есть указатель на лямбду внутри оригинального Sut. Когда мы устанавливаем точку останова внутри метода create, мы также можем заметить, что this указывает на TestTimes$Sut@232a7d73, то есть на исходный Sut, а не на шпионский экземпляр.

РЕДАКТИРОВАТЬ: Хотя этот MCVE, вероятно, не похож на ваш реальный код, на ум приходят следующие решения:

  • Введите поставщика в ваш Sut (либо во время строительства, либо в качестве параметра getData.
  • Создайте поставщика лениво в вашем методе getData (чтобы он указывал на экземпляр mockito)
  • Не используйте поставщика, а просто позвоните create напрямую, если поставщик не пропущен извне
3 голосов
/ 27 июня 2019

Прежде всего, спасибо за хорошо написанный вопрос.

Я сам проверил ваш код и увидел указанную вами ошибку.Хотя я немного изменил ваш код при отладке ... Посмотрите:

    @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от поставщика, что Object будет получено из метода create().И метод create(), вызываемый поставщиком, относится к экземпляру класса, отличному от того, что шпионит Mockito.

Это объясняет, почему вы не можете отследить его с помощью verify.

EDITПо моим исследованиям, это действительно желаемое поведение Поставщика.Он просто создает интерфейс с get() методом, который вызывает любой метод noArgs, который вы объявили в ссылке на метод.Взгляните на this в разделе «Определение поставщика по методике».

...