Utilisation de Mockito pour simuler des classes avec des paramètres génériques

280

Existe-t-il une méthode propre de se moquer d'une classe avec des paramètres génériques? Disons que je dois me moquer d'une classe Foo<T>que je dois passer dans une méthode qui attend a Foo<Bar>. Je peux faire ce qui suit assez facilement:

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

En supposant que getValue()renvoie le type générique T. Mais ça va avoir des chatons quand je le passerai plus tard dans une méthode attendante Foo<Bar>. Le casting est-il le seul moyen d'y parvenir?

Tim Clemons
la source

Réponses:

280

Je pense que vous devez le lancer, mais ça ne devrait pas être trop mal:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
John Paulett
la source
34
Oui mais vous avez toujours un avertissement. Est-ce possible d'éviter l'avertissement?
odwl
12
@SuppressWarnings ("non vérifiée")
qualidafial
18
Je pense que c'est tout à fait acceptable car nous parlons d'un objet simulé dans un test unitaire.
Magnilex
1
@demaniak Cela ne fonctionne pas du tout. Les égaliseurs d'arguments ne peuvent pas être utilisés dans ce contexte.
Krzysztof Krasoń
1
@demaniak Cela se compilera très bien, mais lors de l'exécution du test, il lancera InvalidUseOfMatchersException (qui est une RuntimeException)
Superole
277

Une autre solution consiste à utiliser à la @Mockplace des annotations. Ne fonctionne pas dans tous les cas, mais semble beaucoup plus sexy :)

Voici un exemple:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

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

Le MockitoJUnitRunnerinitialise les champs annotés avec @Mock.

Marek Kirejczyk
la source
3
ceci est déprécié en 1.9.5. :( Semble beaucoup plus propre pour moi.
Code Noviciat
12
@CodeNovitiate Je n'ai trouvé aucune annotation de dépréciation sur MockitoJUnitRunner et Mock dans 1.9.5. Alors, qu'est-ce qui est déconseillé? (Oui, org.mockito.MockitoAnnotations.Mock est déconseillé, mais vous devez utiliser org.mockito.Mock à la place)
neu242
12
Bien joué, cela a parfaitement fonctionné pour moi. Ce n'est pas seulement "plus sexy", cela évite un avertissement sans utiliser SuppressWarnings. Les avertissements existent pour une raison, il vaut mieux ne pas avoir l'habitude de les supprimer. Merci!
Nicole
4
Il y a une chose que je n'aime pas utiliser à la @Mockplace mock(): les champs sont toujours nuls pendant la construction, donc je ne peux pas insérer de dépendances à ce moment-là et ne peux pas rendre les champs définitifs. Le premier peut être résolu par une @Beforeméthode annotée bien sûr.
Rüdiger Schulz,
3
Pour l'initiation, appelez simplement MockitoAnnotations.initMocks (this);
borjab
42

Vous pouvez toujours créer une classe / interface intermédiaire qui satisferait le type générique que vous souhaitez spécifier. Par exemple, si Foo était une interface, vous pouvez créer l'interface suivante dans votre classe de test.

private interface FooBar extends Foo<Bar>
{
}

Dans les situations où Foo est une classe non finale , vous pouvez simplement étendre la classe avec le code suivant et faire la même chose:

public class FooBar extends Foo<Bar>
{
}

Ensuite, vous pouvez utiliser l'un des exemples ci-dessus avec le code suivant:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
dsingleton
la source
4
Pourvu Fooqu'il s'agisse d'une interface ou d'une classe non finale, cela semble être une solution raisonnablement élégante. Merci.
Tim Clemons
J'ai mis à jour la réponse pour inclure également des exemples pour les classes non finales. Idéalement, vous coderiez par rapport à une interface, mais ce ne sera pas toujours le cas. Bonne prise!
dsingleton
16

Créez une méthode utilitaire de test . Particulièrement utile si vous en avez besoin plusieurs fois.

@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
la source
Pourrait étendre votre réponse pour créer une méthode d'utilité générale en passant dans la classe que vous souhaitez simuler.
William Dutton
1
@WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }n'a même pas besoin d'une seule suppression :) Mais attention, Integer num = genericMock(Number.class)compile, mais lance ClassCastException. Ceci n'est utile que pour le G<P> mock = mock(G.class)cas le plus courant .
TWiStErRob
6

Je suis d'accord qu'il ne faut pas supprimer les avertissements dans les classes ou les méthodes car on pourrait ignorer d'autres avertissements supprimés accidentellement. Mais à mon humble avis, il est absolument raisonnable de supprimer un avertissement qui affecte uniquement une seule ligne de code.

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

Voici un cas intéressant: la méthode reçoit une collection générique et retourne une collection générique du même type de base. Par exemple:

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

Cette méthode peut être simulée avec la combinaison de Mockito anyCollectionOf matcher et de Answer.

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
la source