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?

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());

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.


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());

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);
}

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);

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>();
         }
     });
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.