Comment puis-je simuler une méthode void lancer une exception?

J'ai une structure comme ceci:

public class CacheWrapper {
    private Map<Object, Object> innerMap;

    public CacheWrapper() {
        //initialize the innerMap with an instance for an in-memory cache
        //that works on external server
        //current implementation is not relevant for the problem
        innerMap = ...;
    }

    public void putInSharedMemory(Object key, Object value) {
        innerMap.put(key, value);
    }

    public Object getFromSharedMemory(Object key) {
        return innerMap.get(key);
    }
}

Et ma classe client (on pourrait dire qu'il ressemble à ça):

public class SomeClient {
    //Logger here, for exception handling
    Logger log = ...;
    private CacheWrapper cacheWrapper;
    //getter and setter for cacheWrapper...

    public Entity getEntity(String param) {
        Entity someEntity = null;
        try {
            try {
                entity = cacheWrapper.getFromSharedMemory(param);
            } catch (Exception e) {
                //probably connection failure occurred here
                log.warn("There was a problem when getting from in-memory " + param + " key.", e);
            }
            if (entity == null) {
                entity = ...; //retrieve it from database
                //store in in-memory cache
                try {
                    cacheWrapper.put(param, entity);
                } catch (Exception e) {
                    //probably connection failure occurred here
                    log.warn("There was a problem when putting in in-memory " + param + " key.", e);
                }
            }
        } catch (Exception e) {
            logger.error(".......", e);
        }
        return entity;
    }
}

Je suis la création de tests unitaires pour SomeClient#getEntity méthode pour couvrir tous les scénarios. Par exemple, j'ai besoin de couvrir le scénario où il y a des exceptions levées par cacheWrapper. L'approche que je suis est de créer une maquette pour CacheWrapper classe, rendre les méthodes sur CacheWrapper classe de jeter un RuntimeException, définissez cette fantaisie dans une instance de SomeClient et test Someclient#getEntity. Le problème est alors d'essayer de se moquer de putInSharedMemory méthode, car il est void. J'ai essayé beaucoup de façons de le faire, mais aucune de ces travaux. Le projet a des dépendances pour PowerMock et EasyMock.

Voici mes tentatives:

  1. À l'aide de EasyMock.<Void>expect. Cela a soulevé une erreur du compilateur.
  2. Essayé de stub CacheWrapper#putInSharedMemory. N'a pas fonctionné, parce qu'a soulevé une exception avec ce message d'erreur:

    java.lang.AssertionError: Inattendu appel de la méthode putInSharedMemory("foo", com.company.domain.Entity@609fc98)

  3. Ajouté Mockito dépendance au projet de faire usage de la fonctionnalité de PowerMockito classe. Mais cela a soulevé une exception, car il ne s'intègre pas avec EasyMock. C'est l'exception soulevée:

    java.lang.ClassCastException: org.powermock.l'api.easymock.interne.invocationcontrol.EasyMockMethodInvocationControl ne peut pas être lancé pour org.powermock.l'api.mockito.interne.invocationcontrol.MockitoMethodInvocationControl

Voici le code de cette unité de l'échantillon:

@Test
public void getEntityWithCacheWrapperException() {
CacheWrapper cacheWrapper = mockThrowsException();
SomeClient someClient = new SomeClient();
someClient.setCacheWrapper(cacheWrapper);
Entity entity = someClient.getEntity();
//here.....................^
//cacheWrapper.putInSharedMemory should throw an exception
//start asserting here...
}
//...
public CacheWrapper mockThrowsException() {
CacheWrapper cacheWrapper = PowerMock.createMock(CacheWrapper.class);
//mocking getFromSharedMemory method
//this works like a charm
EasyMock.expect(cacheWrapper.getFromSharedMemory(EasyMock.anyObject()))
.andThrow(new RuntimeException("This is an intentional Exception")).anyTimes();
//mocking putInSharedMemory method
//the pieces of code here were not executed at the same time
//instead they were commented and choose one approach after another
//attempt 1: compiler exception: <Void> is not applicable for <void>
EasyMock.<Void>expect(cacheWrapper.putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject()))
.andThrow(new RuntimeException("This is an intentional Exception")).anyTimes();
//attempt 2: stubbing the method
//exception when executing the test:
//Unexpected method call putInSharedMemory("foo", com.company.domain.Entity@609fc98)
Method method = PowerMock.method(CacheWrapper.class, "putInSharedMemory", Object.class, Object.class);
PowerMock.stub(method).toThrow(new RuntimeException("Exception on purpose."));
//attempt 3: added dependency to Mockito integrated to PowerMock
//bad idea: the mock created by PowerMock.createMock() belongs to EasyMock, not to Mockito
//so it breaks when performing the when method
//exception:
//java.lang.ClassCastException: org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl
//cannot be cast to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl
//at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:54)
PowerMockito.doThrow(new RuntimeException("Exception on purpose."))
.when(cacheWrapper).putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject());
PowerMock.replay(cacheWrapper);
return cacheWrapper;
}

Je ne peux pas changer la mise en œuvre de CacheWrapper parce qu'il provient d'un tiers de la bibliothèque. Aussi, je ne peux pas utiliser EasyMock#getLastCall parce que je suis l'exécution de l'essai sur SomeClient#getEntity.

Comment puis-je surmonter cela?

Pourquoi avez-vous besoin PowerMockito à tous? Aucun de vos testé les classes sont définitives, vous pouvez simplement utiliser un spy() sur une instancié exemple... (vous stub espions). Aussi, où est-ce que .replay() vient-il?
Je ne suis pas très habile à l'aide de l'un de ces cadres, parce que j'ai tendance à écrire des tests d'intégration plutôt que la pure tests unitaires. S'il vous plaît pourriez-vous développer davantage sur ce spy() méthode ou toute autre doc?
Dans l'ensemble le code de test est vraiment bizarre, vous semblez être en utilisant à la fois easymock et (pouvoir)mockito... quelle en est la raison?
ajouté powermockito juste parce qu'il a offert une autre solution, mais comme noté dans la tentative 3, c'est une mauvaise idée 🙂
Ainsi, par exemple, avec de la pure mockito, vous pouvez le faire final Foo foo = spy(new Foo()); doThrow(something).when(foo).someMethod(blah)

OriginalL'auteur Luiggi Mendoza | 2015-01-09