Verwenden von Mockito zum Verspotten von Klassen mit generischen Parametern

280

Gibt es eine saubere Methode, eine Klasse mit generischen Parametern zu verspotten? Angenommen, ich muss eine Klasse verspotten, Foo<T>die ich an eine Methode übergeben muss, die a erwartet Foo<Bar>. Ich kann Folgendes leicht genug tun:

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

Angenommen, es wird getValue()der generische Typ zurückgegeben T. Aber das wird Kätzchen haben, wenn ich es später in eine erwartete Methode übergebe Foo<Bar>. Ist Casting das einzige Mittel, um dies zu tun?

Tim Clemons
quelle

Antworten:

280

Ich denke, Sie müssen es besetzen, aber es sollte nicht so schlimm sein:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
John Paulett
quelle
34
Ja, aber Sie haben immer noch eine Warnung. Ist das möglich, um die Warnung zu vermeiden?
Odwl
12
@ SuppressWarnings ("nicht markiert")
qualidafial
18
Ich denke, das ist völlig akzeptabel, da wir in einem Unit-Test über ein Scheinobjekt sprechen.
Magnilex
1
@demaniak Es funktioniert überhaupt nicht. Argument-Matcher können in diesem Kontext nicht verwendet werden.
Krzysztof Krasoń
1
@demaniak Das wird gut kompiliert, aber wenn der Test ausgeführt wird, wird InvalidUseOfMatchersException (was eine RuntimeException ist)
ausgelöst
277

Eine andere Möglichkeit, dies zu @Mockumgehen, besteht darin, stattdessen Anmerkungen zu verwenden. Funktioniert nicht in allen Fällen, sieht aber viel sexy aus :)

Hier ist ein Beispiel:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

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

Das MockitoJUnitRunnerinitialisiert die mit Anmerkungen versehenen Felder @Mock.

Marek Kirejczyk
quelle
3
Dies ist in 1.9.5 veraltet. :( Scheint mir viel sauberer.
Code Novitiate
12
@CodeNovitiate Ich konnte in 1.9.5 keine veralteten Anmerkungen zu MockitoJUnitRunner und Mock finden. Also, was ist veraltet? (Ja, org.mockito.MockitoAnnotations.Mock ist veraltet, aber Sie sollten stattdessen org.mockito.Mock verwenden)
neu242
12
Gut gemacht, das hat perfekt für mich funktioniert. Es ist nicht nur "sexier", es vermeidet eine Warnung ohne Verwendung SuppressWarnings. Warnungen gibt es aus einem Grund, es ist besser, nicht die Gewohnheit zu haben, sie zu unterdrücken. Vielen Dank!
Nicole
4
Es gibt eine Sache, die ich nicht gerne @Mockanstelle von verwende mock(): Die Felder sind während der Erstellungszeit immer noch null, daher kann ich zu diesem Zeitpunkt keine Abhängigkeiten einfügen und die Felder nicht endgültig machen. Ersteres kann @Beforenatürlich durch eine kommentierte Methode gelöst werden .
Rüdiger Schulz
3
Rufen Sie zur Initiierung einfach MockitoAnnotations.initMocks (this) auf.
Borjab
42

Sie können jederzeit eine Zwischenklasse / Schnittstelle erstellen, die dem generischen Typ entspricht, den Sie angeben möchten. Wenn Foo beispielsweise eine Schnittstelle wäre, könnten Sie die folgende Schnittstelle in Ihrer Testklasse erstellen.

private interface FooBar extends Foo<Bar>
{
}

In Situationen, in denen Foo eine nicht endgültige Klasse ist, können Sie die Klasse einfach mit dem folgenden Code erweitern und dasselbe tun:

public class FooBar extends Foo<Bar>
{
}

Dann könnten Sie eines der obigen Beispiele mit dem folgenden Code verwenden:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
Dsingleton
quelle
4
Vorausgesetzt, es Foohandelt sich um eine Schnittstelle oder eine nicht endgültige Klasse, scheint dies eine einigermaßen elegante Lösung zu sein. Vielen Dank.
Tim Clemons
Ich habe die Antwort aktualisiert, um auch Beispiele für nicht abschließende Klassen aufzunehmen. Idealerweise würden Sie gegen eine Schnittstelle codieren, aber das wird nicht immer der Fall sein. Guter Fang!
Dsingleton
16

Erstellen Sie eine Testdienstprogrammmethode . Besonders nützlich, wenn Sie es mehrmals benötigen.

@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
quelle
Könnte Ihre Antwort erweitern, um eine allgemeine Dienstprogrammmethode zu erstellen, die in der Klasse übergeben wird, die Sie verspotten möchten.
William Dutton
1
@ WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }es braucht nicht einmal eine einzige Unterdrückung :) Aber sei vorsichtig, Integer num = genericMock(Number.class)kompiliere, sondern wirft ClassCastException. Dies ist nur für den häufigsten G<P> mock = mock(G.class)Fall nützlich .
TWiStErRob
6

Ich bin damit einverstanden, dass man Warnungen in Klassen oder Methoden nicht unterdrücken sollte, da man andere, versehentlich unterdrückte Warnungen übersehen könnte. Aber meiner Meinung nach ist es absolut vernünftig, eine Warnung zu unterdrücken, die nur eine einzige Codezeile betrifft.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Tobias Uhmann
quelle
3

Hier ist ein interessanter Fall: Die Methode empfängt eine generische Sammlung und gibt eine generische Sammlung desselben Basistyps zurück. Beispielsweise:

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

Diese Methode kann mit einer Kombination aus Mockito anyCollectionOf Matcher und der Antwort verspottet werden.

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
quelle