Utilisation de Mockito pour tester les classes abstraites

j'aimerais tester une classe abstraite. Bien sûr, je peux écrire manuellement une simulation qui hérite de la classe.

est-ce que je peux le faire en utilisant un framework moqueur (J'utilise Mockito) au lieu de fabriquer ma moquerie à la main? Comment?

174
demandé sur Community 2009-07-06 18:40:47

11 réponses

la suggestion suivante testons les classes de résumé sans créer une" vraie "sous - classe-la simulation est la sous-classe.

utiliser Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS) , puis de se moquer de toutes les méthodes abstraites qui sont invoquées.

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 ne avez pour mettre en œuvre les méthodes abstraites, tant qu'elles ne sont jamais invoquées.

à mon avis, c'est mieux que d'utiliser un espion, car un espion a besoin d'une instance, ce qui signifie que vous devez créer une sous-classe instanciable de votre classe abstraite.

279
répondu Morten Lauritsen Khodabocus 2016-06-24 21:22:41

si vous avez juste besoin de tester certaines des méthodes concrètes sans toucher à aucun des résumés, vous pouvez utiliser CALLS_REAL_METHODS (voir réponse de Morten ), mais si la méthode concrète sous test appelle certains des résumés, ou des méthodes d'interface non mises en œuvre, cela ne fonctionnera pas -- Mockito se plaindra " ne peut pas appeler la méthode réelle sur l'interface java."

(Oui, c'est un design nul, mais certains cadres, par exemple Tapestry 4, le forcent sur vous.)

la solution est d'inverser cette approche -- utiliser le comportement simulé ordinaire (c.-à-d., tout est moqué/entaillé) et utiliser doCallRealMethod() pour explicitement appeler la méthode de béton sous essai. 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 ajouter:

pour les méthodes non nulles, vous devrez utiliser thenCallRealMethod() à la place, par exemple:

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

sinon Mockito se plaindra " entaillage non fini détecté."

66
répondu David Moles 2017-05-23 10:31:30

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

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));
16
répondu Richard Nichols 2012-10-18 01:40:09
Les cadres de moquerie

sont conçus pour faciliter la simulation des dépendances de la classe que vous testez. Lorsque vous utilisez un framework moqueur pour simuler une classe, la plupart des Framework créent dynamiquement 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 retourner une fausse valeur.

lorsque vous testez une classe abstraite, vous voulez exécuter les méthodes non-abstraites du sujet sous Test (SUT), donc un framework moqueur n'est pas ce que vous voulez.

une partie de la confusion est que la réponse à la question que vous avez liée à Saïd pour fabriquer à la main une moquerie qui s'étend de votre classe abstraite. Je n'appellerais pas une telle classe une moquerie. Une simulation est une classe qui est utilisée pour remplacer une dépendance, est programmée avec des attentes, et peut être questionnée pour voir si ces attentes sont satisfaites.

à la place, je suggère de définir une sous-classe non abstraite de votre classe abstraite dans votre test. Si c' résultats dans trop de code, que cela peut être un signe que votre classe est difficile à étendre.

une solution alternative serait de rendre votre cas d'essai lui-même abstrait, avec une méthode abstraite pour créer le SUT (en d'autres termes, le cas d'essai utiliserait la Template Method design pattern).

13
répondu NamshubWriter 2016-10-07 00:40:49

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<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> 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.

7
répondu 2009-07-15 20:38:54

ce qui me fait vraiment me sentir mal à propos des moqueries des classes abstraites est le fait, que ni le constructeur par défaut YourAbstractClass() ne soit appelé (missing super() in mock) ni ne semble y avoir aucune façon dans Mockito d'initialiser par défaut les propriétés moqueuses (E. G Liste des propriétés avec ArrayList vide ou LinkedList).

ma classe abstraite (en gros le code source de la classe est généré) ne fournit pas une injection de setter de dépendances pour les éléments de liste, ni un constructeur où il initialise les éléments de la liste (que j'ai essayé d'ajouter manuellement).

Seuls les attributs de classe de l'utilisation d'initialisation par défaut: liste privée dep1 = Nouveau ArrayList; liste privée dep2 = new ArrayList

il N'y a donc aucun moyen de se moquer d'une classe abstraite sans utiliser une implémentation objet réelle (E. g définition de la classe interne dans la classe test unitaire, méthodes abstraites prépondérantes) et espionnage de l'objet réel (ce qui fait une initialisation de champ appropriée).

dommage que seul PowerMock puisse nous aider.

5
répondu Thomas Heiss 2010-11-02 18:30:15

en supposant que vos classes de test sont dans le même paquet (sous une racine source différente) que vos classes sous test, vous pouvez simplement créer la maquette:

YourClass yourObject = mock(YourClass.class);

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

vous devez fournir des attentes pour chaque méthode qui est appelé avec l'attente sur toutes les méthodes concrètes appelant la méthode super - pas sûr comment vous feriez avec Mockito, mais je crois que c'est possible avec EasyMock.

Tout ce que cela fait est de créer une instance concrète de YouClass et de vous épargner l'effort de fournir des implémentations vides de chaque méthode abstraite.

comme mise à part, 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épend de la fonctionnalité fournie par la classe abstraite.

2
répondu Nick Holt 2009-07-28 08:29:17

, 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.
1
répondu DwB 2015-03-13 19:43:07

vous pouvez instancier une classe anonyme, injecter vos moqueries et ensuite 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);
    }
}

n'oubliez pas que la visibilité doit être protected pour la propriété myDependencyService de la classe abstraite ClassUnderTest .

0
répondu Samuel 2017-08-16 14:14:48

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

0
répondu Smart Coder 2018-07-27 13:50:16

Mockito permet de moquer les classes abstraites au moyen de la @Mock annotation:

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 des paramètres du constructeur.

0
répondu Jorge Pastor 2018-08-15 22:19:46