2016-01-13 1 views
6

J'ai le test suivant où je dois vérifier que tous les accesseurs de la classe Person sont appelés. Jusqu'à présent, j'ai utilisé verify() de mockito pour m'assurer que chaque getter est appelé. Y a-t-il un moyen de le faire par réflexion? Il se peut qu'un nouveau getter soit ajouté à la classe Person mais le test le manquera.Vérifiez que toutes les méthodes getter sont appelées

public class GetterTest { 
    class Person{ 

     private String firstname; 
     private String lastname; 

     public String getFirstname() { 
      return firstname; 
     } 

     public String getLastname() { 
      return lastname; 
     } 
    } 

    @Test 
    public void testAllGettersCalled() throws IntrospectionException{ 
     Person personMock = mock(Person.class); 
     personMock.getFirstname(); 
     personMock.getLastname(); 

     for(PropertyDescriptor property : Introspector.getBeanInfo(Person.class).getPropertyDescriptors()) { 
      verify(personMock, atLeast(1)).getFirstname(); 
      //**How to verify against any getter method and not just getFirstName()???** 
     } 
    } 
} 

Répondre

5

En général, ne moque pas de la classe en cours de test . Si votre test concerne une personne, vous ne devriez jamais voir Mockito.mock(Person.class) dedans, car c'est un signe assez clair que vous testez le cadre de simulation au lieu du système sous test. Au lieu de cela, vous pouvez créer un spy(new Person()), qui créera une implémentation Real Person en utilisant un constructeur réel, puis copiera ses données dans un proxy généré par Mockito. Vous pouvez utiliser MockingDetails.getInvocations() pour vérifier de manière réfléchie que chaque getter a été appelé.

// This code is untested, but should get the point across. Edits welcome. 
// 2016-01-20: Integrated feedback from Georgios Stathis. Thanks Georgios! 

@Test 
public void callAllGetters() throws Exception { 
    Person personSpy = spy(new Person()); 
    personSpy.getFirstname(); 
    personSpy.getLastname(); 

    assertAllGettersCalled(personSpy, Person.class); 
} 

private static void assertAllGettersCalled(Object spy, Class<?> clazz) { 
    BeanInfo beanInfo = Introspector.getBeanInfo(clazz); 
    Set<Method> setOfDescriptors = beanInfo.getPropertyDescriptors() 
     .stream() 
     .map(PropertyDescriptor::getReadMethod) 
     .filter(p -> !p.getName().contains("getClass")) 
     .collect(Collectors.toSet()); 
    MockingDetails details = Mockito.mockingDetails(spy); 
    Set<Method> setOfTestedMethods = details.getInvocations() 
     .stream() 
     .map(InvocationOnMock::getMethod) 
     .collect(Collectors.toSet()); 
    setOfDescriptors.removeAll(setOfTestedMethods); 
    // The only remaining descriptors are untested. 
    assertThat(setOfDescriptors).isEmpty(); 
} 

Il pourrait y avoir un moyen d'appeler verify et invoke sur l'espion généré Mockito, mais qui semble très fragile, et très dépendante de Mockito internes. En outre, tester des getters de style haricot semble être une utilisation inhabituelle du temps/de l'effort. En général, concentrez-vous sur le test des implémentations susceptibles de changer ou de casser.

+0

Great! En ce qui concerne votre dernier conseil, je pense que le test n'est pas pour Person mais pour une classe comme PersonBuilder, PersonCloner ou autre – Raffaele

+0

@Raffaele: Vrai, mais j'espère que la meilleure solution est de vérifier que chaque champ est copié, ce qui implique (mais pas besoin) que chaque getter a été appelé. –

+1

Cela a effectivement travaillé avec un minimum d'effort pour vérifier l'appel de toutes les méthodes getter. Certains changements cependant: 'BeanInfo beanInfo = Introspector.getBeanInfo (Person.class);' et ajouté pour 'setOfDescriptors' un filtre avec:' .filter (p ->! P.contains ("getClass")) 'de sorte que cette méthode n'est pas pris en considération. –

0

Je peux penser à deux solutions pour votre problème:

  1. Générez le code Builder programme, de sorte que vous n'avez pas besoin d'effectuer des tests. Le code Java est généré par un programme et jamais édité par un utilisateur. Testez le générateur à la place. Utilisez un modèle de texte et créez des définitions à partir d'un modèle de domaine sérialisé ou directement à partir de classes compilées Java (vous aurez besoin d'un module séparé dépendant du bean)

  2. Ecrivez vos tests sur une bibliothèque proxy. Le problème est que les proxys réguliers peuvent seulement implémenter des interfaces, pas des classes régulières, et il est très difficile d'avoir des interfaces pour les Javabeans. Si vous choisissez cette route, j'irais avec Javassist. J'ai codé un échantillon exécutable et le mettre on GitHub. Les cas de test utilisent une usine proxy pour instancier les haricots (au lieu d'utiliser new)

public class CountingCallsProxyFactory { 

    public <T> T proxy(Class<T> classToProxy) { 
     ProxyFactory factory = new ProxyFactory(); 
     factory.setSuperclass(classToProxy); 
     Class clazz = factory.createClass(); 
     T instance = (T) clazz.newInstance(); 
     ProxyObject proxy = (ProxyObject) instance; 
     MethodCallCounter handler = new MethodCallCounter(); 
     proxy.setHandler(handler); 
     return instance; 
    } 

    public void verifyAllGettersCalled(Object bean) { 
     // Query the counter against the properties in the bean 
    } 
} 

Le compteur est conservé à l'intérieur de la classe MethodCallCounter

+0

La génération du générateur Person, puis le test du générateur semble être une solution. Le second avec les objets proxy semble un peu plus compliqué mais je voudrais essayer et essayer le Javassist. Merci pour l'exemple de code! –

+0

Je privilégie l'approche générateur, mais encore une fois soit vous utilisez un langage autre que Java pour définir les beans et les constructeurs, ou vous aurez au moins trois modules de compilation: les beans, les builders et l'application – Raffaele