Использование Mockito для моделирования классов с общими параметрами

280

Есть ли чистый метод насмешки над классом с общими параметрами? Скажем, я должен смоделировать класс, Foo<T>который мне нужно передать в метод, который ожидает Foo<Bar>. Я могу сделать следующее достаточно легко:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

Предполагая, getValue()возвращает универсальный тип T. Но это будет иметь котят, когда я позже передам это в метод ожидания Foo<Bar>. Является ли кастинг единственным средством сделать это?

Тим Клемонс
источник

Ответы:

280

Я думаю, вам нужно разыграть его, но это не должно быть слишком плохо:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
Джон Полетт
источник
34
Да, но у вас все еще есть предупреждение. Можно ли избежать предупреждения?
odwl
12
@SuppressWarnings ("unchecked")
квалификационный выпуск
18
Я думаю, что это вполне приемлемо, поскольку речь идет о фиктивном объекте в модульном тесте.
Magnilex
1
@demaniak Это не работает вообще. Сопоставители аргументов не могут быть использованы в этом контексте.
Кшиштоф Красонь
1
@demaniak Это прекрасно скомпилируется, но при запуске теста выдает InvalidUseOfMatchersException (который является RuntimeException)
Superole
277

Еще один способ обойти это - использовать @Mockаннотацию. Не работает во всех случаях, но выглядит намного сексуальнее :)

Вот пример:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

MockitoJUnitRunnerИнициализирует поля аннотированные с @Mock.

Марек Кирейчик
источник
3
это устарело в 1.9.5. :( Мне кажется намного чище.
Код Novitiate
12
@CodeNovitiate Я не смог найти аннотации к устареванию на MockitoJUnitRunner и Mock в 1.9.5. Итак, что является устаревшим? (Да, org.mockito.MockitoAnnotations.Mock устарела, но вместо этого следует использовать org.mockito.Mock)
neu242
12
Хорошо, это отлично сработало для меня. Он не просто «сексуальнее», он избегает предупреждения без использования SuppressWarnings. Предупреждения существуют по причине, лучше не иметь привычки подавлять их. Спасибо!
Николь
4
Есть одна вещь, которая мне не нравится использовать @Mockвместо mock(): поля все еще являются пустыми во время построения, поэтому я не могу вставлять зависимости в это время и не могу сделать поля окончательными. Первый может быть решен с помощью @Beforeаннотированного метода, конечно.
Рюдигер Шульц
3
Для инициации просто вызовите MockitoAnnotations.initMocks (this);
Борхаб
42

Вы всегда можете создать промежуточный класс / интерфейс, который будет удовлетворять универсальному типу, который вы хотите указать. Например, если Foo был интерфейсом, вы можете создать следующий интерфейс в своем тестовом классе.

private interface FooBar extends Foo<Bar>
{
}

В ситуациях, когда Foo не является финальным классом, вы можете просто расширить класс следующим кодом и сделать то же самое:

public class FooBar extends Foo<Bar>
{
}

Тогда вы можете использовать любой из приведенных выше примеров с помощью следующего кода:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
dsingleton
источник
4
При условии, Fooчто это интерфейс или не финальный класс, это представляется довольно элегантным решением. Спасибо.
Тим Клемонс
Я обновил ответ, добавив примеры и для нефинальных классов. В идеале вы должны писать на интерфейсе, но это не всегда так. Хороший улов!
Дсинглтон
16

Создайте метод тестовой утилиты . Особенно полезно, если вам это нужно более одного раза.

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}
acdcjunior
источник
Можно расширить ваш ответ, чтобы сделать общий метод утилит, передаваемый в классе, который вы хотите смоделировать.
Уильям Даттон
1
@WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }это даже не нуждается в единственном подавлении :) Но будьте осторожны, Integer num = genericMock(Number.class)компилируйте, но бросайте ClassCastException. Это полезно только для наиболее распространенного G<P> mock = mock(G.class)случая.
TWiStErRob
6

Я согласен с тем, что не следует подавлять предупреждения в классах или методах, поскольку можно пропустить другие, случайно подавленные предупреждения. Но ИМХО абсолютно разумно подавить предупреждение, которое затрагивает только одну строку кода.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Тобиас Уман
источник
3

Вот интересный случай: метод получает универсальную коллекцию и возвращает универсальную коллекцию того же базового типа. Например:

Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);

Этот метод может быть смоделирован с помощью сочетания Mockito anyCollectionOf и Ответа.

when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
     new Answer<Collection<Assertion>>() {
         @Override
         public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
             return new ArrayList<Assertion>();
         }
     });
qza
источник