Почему Mockito @Mock создает не фиктивный экземпляр? - PullRequest
0 голосов
/ 06 марта 2020

У меня есть несколько очень похожих проектов, все Java, SpringBoot и Maven. Все они имеют один класс с одинаковым именем и почти одинаковым содержимым. Я добавил дополнительный метод в один из них, класс с проблемой, которую я собираюсь описать, но я почти уверен, что детали - это совпадение.

Каждый проект также имеет тестовый класс, соответствующий этому очень похожий класс, и скелет этого тестового класса идентичен в каждом классе. Тестовый класс имеет @InjectMocks для тестируемого класса (CUT) и две аннотации @Mock, одна из которых соответствует переменной экземпляра CUT.

Тестовый класс имеет @Before метод, который создает переменную экземпляра, используемую тестами.

Все вариации класса теста имеют "@RunWith(MockitoJUnitRunner.class)".

Если я запускаю один из "хороших" тестов и задаю точка останова в первой строке метода @Before, а затем просмотр переменных «this» на панели переменных. Я вижу типы двух переменных экземпляра @ Mock-ed, заканчивающихся на «$ MockitoMock».

Если я делаю то же самое в «плохом» тесте, типы двух переменных @ Mock-ed НЕ заканчиваются на «$MockitoMock». Фактически, это нормальные экземпляры соответствующих классов, а не фиктивных классов.

Еще более любопытно, что в «плохом» тесте я пытался делать явные вызовы «instvar = mock(clazz.class)» в @Before метод, и после того, как я перешагну через них, тип переменной экземпляра будет STILL, а не фиктивный тип, ОДНАКО, когда я нажимаю на переменную экземпляра, на панели toString отображается «Mock for ..., hashCode: 1028811481». Если я «Возобновлю» в этот момент, я могу достичь точки останова в якобы имитируемом классе с тем же экземпляром, значение toString которого говорит «Mock for ...».

Это проблема словами. Теперь, я думаю, я покажу некоторый код.

Вот часть "плохого" класса теста:

@RunWith(MockitoJUnitRunner.class)
public class RestClientTest {
    @InjectMocks
    RestClient restClient;

    @Mock
    RestClientFactory restClientFactory;

    @Mock
    RestTemplate restTemplate;

    HttpEntity<String> requestEntity;

    @Before
    public void setup() {
        requestEntity = new HttpEntity<>(new HttpHeaders());
        restClientFactory   = mock(RestClientFactory.class);
        restTemplate        = mock(RestTemplate.class);
        ReflectionTestUtils.setField(restClient, "restClientFactory", restClientFactory);
    }

Вот часть "хорошего" класса теста:

@RunWith(MockitoJUnitRunner.class)
public class RestClientTest {
    @InjectMocks
    RestClient restClient;

    @Mock
    RestClientFactory restClientFactory;

    @Mock
    RestTemplate restTemplate;

    HttpEntity<String> requestEntity;

    @Before
    public void setup() {
        requestEntity = new HttpEntity<>(new HttpHeaders());
    }

Я определил, что как "хорошие", так и "плохие" проекты используют версию 2.15.0 mockito-core.

Обновление :

I попытался войти в «ложный» вызов в плохом тесте и установить там точки останова, потому что он идет здесь от обработки аннотации, чтобы я мог видеть поведение как плохого, так и хорошего случая.

Вот что я видел в хорошем случае:

first breakpoint of good case

Я перешел к строке 65 и вошел в «createMock ()». Это поместило меня в класс MockUtil:

in MockUtil.createMock

Тип «mockMaker» - «org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker».

Я перешел к строке 35 и перешел к методу mockMaker.createMock ():

In BBMM.createMock()

Теперь давайте начнем сначала и запустите «плохой» случай:

Сначала мы достигли начальной точки останова: first breakpoint in bad case

А затем здесь: second breakpoint in bad case

Теперь мы видим, что тип «mockMaker» отличается от хорошего случая. Тип "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker".

Я не собираюсь продолжать проходить через это, но этот путь действительно производит "поддельный макет" с другим значением toString .

Теперь, когда я думаю об этом, этот экземпляр похож на «шпиона» в том смысле, что им управляет Mockito, но по умолчанию все методы вызывают исходный метод класса. Я понятия не имею, почему здесь идет другой путь.

Я надеюсь, что этой информации будет достаточно, чтобы дать подсказку тому, кто лучше понимает, как это работает.

1 Ответ

2 голосов
/ 06 марта 2020

Тип «mockMaker» [в «хорошем» случае] - «org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker».

Сейчас [в «плохой» случай] мы видим, что тип «mockMaker» отличается от хорошего случая. Тип "org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker".

Итак, "хороший" проект использует макет по умолчанию, который использует подклассы - см. ByteBuddyMockMaker . java - в то время как «плохой» проект использует макет не по умолчанию, который пытается использовать Java инструментарий , чтобы избежать создания подклассов: InlineByteBuddyMockMaker. java. Это соответствует разнице в поведении, которую вы наблюдали

Согласно Javado c для InlineByteBuddyMockMaker:

Этот макет должен быть явно активирован для поддержка окончательных типов и методов насмешек:

Этот создатель насмешек можно активировать, создав файл /mockito-extensions/org.mockito.plugins.MockMaker, содержащий текст mock-maker-inline или org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.

. почему это происходит, вам нужно поискать в вашем classpath, чтобы выяснить, как ресурс /mockito-extensions/org.mockito.plugins.MockMaker заканчивается там.

...