Maintenant que j'ai fini les articles qui parlaient du fonctionnement de Mockito, je voulais revenir sur le problème qui est à l'origine de ce travail.
Mon but était de créer un mock de la classe DefaultMessageListenerContainer de Spring et de faire un stub de la méthode isRunning(). Cette méthode a une particularité, c'est qu'elle est définie en final.public final boolean isRunning() { synchronized (this.lifecycleMonitor) { return (this.running && runningAllowed()); } }
Nous ne pouvons pas utiliser Mockito dans ce cas car il ne permet de simuler le comportement d'une méthode final. Je vais reprendre tout le cheminement du problème et les différents essais que j'ai fait.
Un exemple de code
Tous les exemples exposés ici sont disponibles sur Github. J'ai défini une classe DefaultMessagerServiceImpl
public class DefaultMessagerServiceImpl { protected final Object lifecycleMonitor = new Object(); public final boolean isRunning(){ synchronized (this.lifecycleMonitor) { System.out.println("Send a mail"); return true; } } }
Cette classe est à l'image de ce que l'on retrouve dans Spring. Si j'essaye de mocker cet objet dans un test j'obtiens
@RunWith(MockitoJUnitRunner.class) public class FinalMethodTest { @InjectMocks private BlogArticleService blogArticleService = new BlogArticleServiceImpl(); @Mock private DefaultMessagerServiceImpl defaultMessagerService; @Test(expected = NullPointerException.class) public void test(){ when(defaultMessagerService.isRunning()).thenReturn(false); assertThat(blogArticleService.write("title", "my new content")).isEqualTo(false); } }
mais j'obtiens aussi l'exception
java.lang.NullPointerException
at com.javamind.model.DefaultMessagerServiceImpl.isRunning(DefaultMessagerServiceImpl.java:14)
En gros même si je mocke mon objet je rentre dans l'implémentation de cette méthode finale. Comme on est dans un proxy l'objet lifecycleMonitor est null.
Si je simplifie le code
public class DefaultMessagerServiceImpl { public final boolean isRunning(){ System.out.println("Send a mail"); return true; } }
l'exécution du test donnera l'exception suivanteCaused by: org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.
3. the parent of the mocked class is not public.
It is a limitation of the mock engine.
Tout est dit dans le code : Mockito ne peut pas stubber une méthode en final.
Comment résoudre ce problème
La solution ne se trouve pas au niveau de Mockito mais plutôt au niveau de PowerMock qui permet d'étendre Mockito. Au niveau des dépendances vous pouvez ajouter
testCompile 'org.powermock:powermock-module-junit4:1.5.5'
testCompile 'org.powermock:powermock-api-mockito:1.5.5'
Ensuite vous n'avez plus qu'à adapter votre test. Dans l'exemple ci dessous je mixe à la fois PowerMock et Mockito.
@RunWith(PowerMockRunner.class) @PrepareForTest( { DefaultMessagerServiceImpl.class }) public class FinalMethodCas2Test { @InjectMocks private BlogArticleServiceImpl blogArticleService = new BlogArticleServiceImpl(); @Mock private BlogExporterService mockElogExporterService; private DefaultMessagerServiceImpl defaultMessagerService; @Before public void setUp(){ MockitoAnnotations.initMocks(this); defaultMessagerService = PowerMockito.mock(DefaultMessagerServiceImpl.class); blogArticleService.setMessagerService(defaultMessagerService); } @Test public void test(){ when(mockElogExporterService.socialBroadcast(any(Article.class))).thenReturn(3); PowerMockito.when(defaultMessagerService.isRunning()).thenReturn(false); assertThat(blogArticleService.write("title", "my new content")).isEqualTo(false); } }
Voila vous avez maintenant une solution pour pouvoir créer un mock et stubber une méthode finale.
PowerMock utilise son propre classe loader et fait de la manipulation de bytecode pour permettre de créer des mocks et stubber les méthodes statiques, les constructeurs, les classes finales, les méthodes finales ou privées... J'ai utilisé ici l'extension pour Mockito mais il en existe aussi une pour EasyMock.

Aucun commentaire:
Enregistrer un commentaire
Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.