Utiliser Mockito pour tester des classes abstraites

Je voudrais tester un cours de résumé. Bien sûr, je peux écrire manuellement un simulacre qui hérite de la classe.

Puis-je le faire en utilisant un cadre moqueur (j’utilise Mockito) au lieu de fabriquer mon maquette à la main? Comment?

La suggestion suivante vous permet de tester des classes abstraites sans créer de “vraie” sous-classe – le Mock est la sous-classe.

utilisez Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS) , puis Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS) toute méthode abstraite appelée.

Exemple:

 public abstract class My { public Result methodUnderTest() { ... } protected abstract void methodIDontCareAbout(); } public class MyTest { @Test public void shouldFailOnNullIdentifiers() { My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS); Assert.assertSomething(my.methodUnderTest()); } } 

Note: La beauté de cette solution est que vous n’avez pas à implémenter les méthodes abstraites, tant qu’elles ne sont jamais invoquées.

À mon avis, cela est plus simple que d’utiliser un espion, car un espion nécessite une instance, ce qui signifie que vous devez créer une sous-classe instantanée de votre classe abstraite.

Si vous avez juste besoin de tester certaines des méthodes concrètes sans toucher aux résumés, vous pouvez utiliser CALLS_REAL_METHODS (voir la réponse de Morten ), mais si la méthode concrète testée appelle certains des résumés ou des méthodes d’interface non implémentées, work – Mockito va se plaindre “Impossible d’appeler la méthode réelle sur l’interface java.”

(Oui, c’est une conception moche, mais certains frameworks, par exemple Tapestry 4, le forcent à le faire.)

La solution de contournement consiste à inverser cette approche – utilisez le comportement simulé ordinaire (c.-à-d. Tout est simulé / remplacé) et utilisez doCallRealMethod() pour appeler explicitement la méthode concrète sous test. Par exemple

 public abstract class MyClass { @SomeDependencyInjectionOrSomething public abstract MyDependency getDependency(); public void myMethod() { MyDependency dep = getDependency(); dep.doSomething(); } } public class MyClassTest { @Test public void myMethodDoesSomethingWithDependency() { MyDependency theDependency = mock(MyDependency.class); MyClass myInstance = mock(MyClass.class); // can't do this with CALLS_REAL_METHODS when(myInstance.getDependency()).thenReturn(theDependency); doCallRealMethod().when(myInstance).myMethod(); myInstance.myMethod(); verify(theDependency, times(1)).doSomething(); } } 

Mis à jour pour append:

Pour les méthodes non-vides, vous devez utiliser thenCallRealMethod() , par exemple:

 when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod(); 

Sinon, Mockito se plaindra de la présence d’un “stubbing inachevé détecté”.

Vous pouvez y parvenir en utilisant un espion (utilisez la dernière version de Mockito 1.8+).

 public abstract class MyAbstract { public Ssortingng concrete() { return abstractMethod(); } public abstract Ssortingng abstractMethod(); } public class MyAbstractImpl extends MyAbstract { public Ssortingng abstractMethod() { return null; } } // your test code below MyAbstractImpl abstractImpl = spy(new MyAbstractImpl()); doReturn("Blah").when(abstractImpl).abstractMethod(); assertTrue("Blah".equals(abstractImpl.concrete())); 

Les frameworks de simulation sont conçus pour simplifier la modélisation des dépendances de la classe que vous testez. Lorsque vous utilisez un cadre moqueur pour simuler une classe, la plupart des frameworks créent dynamicment une sous-classe et remplacent l’implémentation de la méthode par du code pour détecter quand une méthode est appelée et renvoyer une valeur fausse.

Lorsque vous testez une classe abstraite, vous souhaitez exécuter les méthodes non abstraites de Subject Under Test (SUT), de sorte qu’un cadre de simulation ne soit pas ce que vous voulez.

Une partie de la confusion est que la réponse à la question que vous avez liée à dit à la main de fabriquer un simulacre qui s’étend de votre classe abstraite. Je n’appellerais pas une telle classe un simulacre. Un simulacre est une classe qui remplace une dépendance, est programmé avec des attentes et peut être interrogé pour voir si ces attentes sont satisfaites.

Au lieu de cela, je suggère de définir une sous-classe non abstraite de votre classe abstraite dans votre test. Si cela se traduit par trop de code, cela peut être un signe que votre classe est difficile à étendre.

Une solution alternative consisterait à rendre votre cas de test lui-même abstrait, avec une méthode abstraite pour créer le SUT (en d’autres termes, le scénario de test utiliserait le modèle de conception de la méthode de modèle).

Essayez d’utiliser une réponse personnalisée.

Par exemple:

 import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class CustomAnswer implements Answer { public Object answer(InvocationOnMock invocation) throws Throwable { Answer answer = null; if (isAbstract(invocation.getMethod().getModifiers())) { answer = Mockito.RETURNS_DEFAULTS; } else { answer = Mockito.CALLS_REAL_METHODS; } return answer.answer(invocation); } } 

Il retournera la maquette pour les méthodes abstraites et appellera la méthode réelle pour les méthodes concrètes.

Ce qui me fait vraiment de la peine de moquer des classes abstraites est le fait que ni le constructeur par défaut YourAbstractClass () ne soit appelé (il manque des super () dans mock) ni Mockito par défaut pour initialiser des propriétés de mock avec ArrayList vide ou LinkedList).

Ma classe abstraite (en gros le code source de la classe est généré) ne fournit PAS d’injection de setter de dépendance pour les éléments de liste, ni de constructeur où il initialise les éléments de liste (que j’ai essayé d’append manuellement).

Seuls les atsortingbuts de classe utilisent l’initialisation par défaut: private List dep1 = new ArrayList; Liste privée dep2 = new ArrayList

Il n’y a donc AUCUN moyen de simuler une classe abstraite sans utiliser une implémentation d’object réelle (par exemple, définition de classe interne dans une classe de test unitaire, substitution de méthodes abstraites) et espacement de l’object réel (qui initialise correctement les champs).

Dommage que seul PowerMock aiderait davantage ici.

En supposant que vos classes de test se trouvent dans le même package (sous une racine source différente) que vos classes testées, vous pouvez simplement créer le modèle:

 YourClass yourObject = mock(YourClass.class); 

et appelez les méthodes que vous souhaitez tester comme vous le feriez pour toute autre méthode.

Vous devez fournir des attentes pour chaque méthode appelée avec l’attente de toute méthode concrète appelant la méthode super – vous ne savez pas comment faire avec Mockito, mais je pense que c’est possible avec EasyMock.

Tout ce que vous faites, c’est créer une instance concrète de YouClass et vous YouClass de fournir des implémentations vides de chaque méthode abstraite.

En passant, je trouve souvent utile d’implémenter la classe abstraite dans mon test, où elle sert d’exemple d’implémentation que je teste via son interface publique, bien que cela dépende des fonctionnalités fournies par la classe abstraite.

Vous pouvez étendre la classe abstraite avec une classe anonyme dans votre test. Par exemple (en utilisant Junit 4):

 private AbstractClassName classToTest; @Before public void preTestSetup() { classToTest = new AbstractClassName() { }; } // Test the AbstractClassName methods. 

Vous pouvez instancier une classe anonyme, injecter vos simulacres et tester cette classe.

 @RunWith(MockitoJUnitRunner.class) public class ClassUnderTest_Test { private ClassUnderTest classUnderTest; @Mock MyDependencyService myDependencyService; @Before public void setUp() throws Exception { this.classUnderTest = getInstance(); } private ClassUnderTest getInstance() { return new ClassUnderTest() { private ClassUnderTest init( MyDependencyService myDependencyService ) { this.myDependencyService = myDependencyService; return this; } @Override protected void myMethodToTest() { return super.myMethodToTest(); } }.init(myDependencyService); } } 

Gardez à l’esprit que la visibilité doit être protected pour la propriété myDependencyService de la classe abstraite ClassUnderTest .

Whitebox.invokeMethod (..) peut être utile dans ce cas.

Mockito permet de simuler des classes abstraites à l’aide de l’annotation @Mock :

 public abstract class My { public abstract boolean myAbstractMethod(); public void myNonAbstractMethod() { // ... } } @RunWith(MockitoJUnitRunner.class) public class MyTest { @Mock(answer = Answers.CALLS_REAL_METHODS) private My my; @Test private void shouldPass() { BDDMockito.given(my.myAbstractMethod()).willReturn(true); my.myNonAbstractMethod(); // ... } } 

L’inconvénient est qu’il ne peut pas être utilisé si vous avez besoin de parameters de constructeur.