Vous connaissez peut-être déjà Mockito ? En fait vous connaissez même surement pour ceux qui ont eu la chance d'être présent à la première soirée Marmite des JDuchess.
Et pourquoi j'en parle ? Bon, pas seulement parce que leur logo est un mojito géant et que j'adore cette boisson (*), mais tout simplement pour présenter quelques astuces d'utilisation avec PowerMock.
* inconsciemment ca doit jouer un peu quand même
Petit retour aux sources, selon le site officiel : "Mockito is a mocking framework that tastes really good."
Ca c'est pour ceux qui connaissent EasyMock, pas besoin de préciser le comportement attendu de votre mock dans ses moindres détails. On se focalise juste sur le resultat produit par l'objet testé, pas sur sa facon d'utiliser le mock. (A noter que EasyMock permet désormais de créer des mocks sans nécessité de préciser son comportement avec createNiceMock)
Parce que Mockito tout seul ne permet pas de gérer quelques cas sur du code "intestable". Et ce que j'appelle intestable, alors ça, je suis sur que vous en avez déjà vu (ou alors vous avez une veine incroyable et je veux savoir où vous travaillez !) :
Pour info, vous avez déjà un bon article sur le sujet mais concernant JMockit : http://fchabanois.developpez.com/tutorial/java/jmockit/
Tout simplement parce que quand on parle de test unitaire, on fait référence à des tests UNITAIRES ! Et tester unitairement un objet ça veut dire le tester en isolation, donc pas d'appel à une base de données, à une file de messages etc... Dans l'idéal, un test unitaire ne teste qu'une seule classe. On utilise d'ailleurs une convention simple et assez parlante pour cela : MonObjet est testé par MonObjetTest (ou TestMonObjet).
Point personnel, sur ce dernier point, je suis plus coulant. Un test reste unitaire du moment que je n'utilise pas de ressources externes (base de données etc...) mais j'accepte l'utilisation d'autre classes du moment que je maitrise leurs propres interactions.
Et donc pour tester en isolation nous allons avoir besoin des fausses implémentations de toutes les dépendances de notre objet. Nous pourrions les écrire toutes, en admettant que chaque dépendance soit utilisé via une interface mais ce serait très fastidieux. Utiliser une librairie de mock permettra donc d'éviter une tâche bien pénible.
Mockito est fourni avec runner Junit que l'on peut activer comme suit :
@RunWith(MockitoJUnitRunner.class) public class TestMyObjet { @InjectMocks private MyObject myObjet = new MyObject();
@Mock private MyComplexObject myComplexObject;
@Mock private IMyComplexInterface anotherComplexObject ; ... }
Première bonne nouvelle, en utilisant l'annotation @Mock, le runner Mockito va créer pour vous tout les mocks qui sont déclarés que ce soit des interfaces ou des classes.
Là ou le runner est encore plus fort, c'est qu'il va injecter tout les mocks nécessaires sur l'objet annoté par @InjectMocks. Et là c'est très pratique, surtout si vous avez du code sans setter.
public class MyObjet { ... @Autowired private MyComplexObjet myComplexObjet; ...
myComplexObjet est privé et n'a aucun setter, donc sans le runner Mockito vous ne pourriez pas l'injecter.
Bon en fait, si comme nous allons le voir.
D'ailleurs dans le cas suivant Mockito ne vous sera d'aucune aide et du coup nous allons quand même avoir recours à des ruses de sioux :
public class MyObjet { @Value("${onekey}") private String onekey; @Value("${anotherkey}") private String anotherkey; ... }
Dans ce cas, vous pouvez utiliser Spring à la rescousse et ReflectionTestUtils :
@Before public void initialize () { ReflectionTestUtils.setField(myObject, "onekey", "one"); ReflectionTestUtils.setField(myObject, "anotherkey", "two"); }
C'est pas du Mockito mais ça valait le coup d'être cité.
En fait Mockito n'est pas capable de mocker une méthode statique par lui même. Pour cela il va falloir appeler PowerMock à la rescousse.
Pour l'activer, vous allez désormais utiliser un autre runner : PowerMockRunner.
@RunWith(PowerMockRunner.class) @PrepareForTest({ Lookup.class }) @PowerMockIgnore("org.apache.commons.logging.*") public class TestMyObject { ... @Before public void initialize() throws Exception { // Comme vous n'utilisez pas MockitoRunner, vous devez faire appel à initMocks pour initialiser vos mocks MockitoAnnotations.initMocks(this); ... // o utilise PowerMockito pour mocker l'appel statique à Lookup.getInstance() // ------------------------------------------------------------------- PowerMockito.mockStatic(Lookup.class); Lookup lookup = mock(Lookup.class); when(Lookup.getInstance()).thenReturn(lookup); ... }
Explications de texte :
Activation du runner :
@RunWith(PowerMockRunner.class)
Liste des objets dont on va faire un mock de méthode statiques :
@PrepareForTest({ Lookup.class })
La ligne suivante est nécessaire car on n'utilise plus le runner MockitoJUnitRunner mais que l'on souhaite quand même utiliser les annotations Mocks et InjectMocks
MockitoAnnotations.initMocks(this);
et enfin, le mock de la méthode statique :
PowerMockito.mockStatic(Lookup.class); Lookup lookup = mock(Lookup.class); when(Lookup.getInstance()).thenReturn(lookup );
Facile non ?
Allez, une petite dernière pour la route. Et si mon objet doit implémenter deux interfaces ? Par exemple j'utilise Carbon pour l'injection de dépendance (ok on sent le projet legacy là) et du coup tout mes beans doivent aussi implémenter l'interface Component.
Ouf, Mockito le gère depuis 2009 :
mock(Lookup.class, withSettings().extraInterfaces(Component.class));
Voila, vous en savez (peut-être) un peu plus sur Mockito, à vous de jouer maintenant !