web-dev-qa-db-fra.com

Mocking Java Enum pour ajouter une valeur à tester en cas d'échec

J'ai un commutateur enum plus ou moins comme ceci:

public static enum MyEnum {A, B}

public int foo(MyEnum value) {
    switch(value) {
        case(A): return calculateSomething();
        case(B): return calculateSomethingElse();
    }
    throw new IllegalArgumentException("Do not know how to handle " + value);
}

et j'aimerais que toutes les lignes soient couvertes par les tests, mais comme le code doit traiter toutes les possibilités, je ne peux pas fournir de valeur sans l'instruction de cas correspondante dans le commutateur.

Il n'est pas possible d'étendre l'énumération pour ajouter une valeur supplémentaire, et se moquer de la méthode equals pour renvoyer false ne fonctionnera pas non plus, car le bytecode généré utilise une table de sauts derrière les rideaux pour aller au bon cas ... pensait que peut-être une certaine magie noire pourrait être réalisée avec PowerMock ou quelque chose.

Merci!

modifier :

En tant que propriétaire de l'énumération, j'ai pensé que je pouvais simplement ajouter une méthode aux valeurs et ainsi éviter complètement le problème du commutateur; mais je laisse la question car c'est toujours intéressant.

46
fortran

Voici un exemple complet.

Le code est presque identique à votre original (simplement une meilleure validation de test):

public enum MyEnum {A, B}

public class Bar {

    public int foo(MyEnum value) {
        switch (value) {
            case A: return 1;
            case B: return 2;
        }
        throw new IllegalArgumentException("Do not know how to handle " + value);
    }
}

Et voici le test unitaire avec une couverture complète du code, le test fonctionne avec Powermock (1.4.10), Mockito (1.8.5) et JUnit (4.8.2):

@RunWith(PowerMockRunner.class)
public class BarTest {

    private Bar bar;

    @Before
    public void createBar() {
        bar = new Bar();
    }

    @Test(expected = IllegalArgumentException.class)
    @PrepareForTest(MyEnum.class)
    public void unknownValueShouldThrowException() throws Exception {
        MyEnum C = PowerMockito.mock(MyEnum.class);
        Whitebox.setInternalState(C, "name", "C");
        Whitebox.setInternalState(C, "ordinal", 2);

        PowerMockito.mockStatic(MyEnum.class);
        PowerMockito.when(MyEnum.values()).thenReturn(new MyEnum[]{MyEnum.A, MyEnum.B, C});

        bar.foo(C);
    }

    @Test
    public void AShouldReturn1() {
        assertEquals(1, bar.foo(MyEnum.A));
    }

    @Test
    public void BShouldReturn2() {
        assertEquals(2, bar.foo(MyEnum.B));
    }
}

Résultat:

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.628 sec
42
Jonny Heggheim

@Melloware

... code qui exécute l'instruction switch () Java lance un fichier Java.lang.ArrayIndexOutOfBounds ...

J'ai le même problème. Exécutez votre test avec new Enum en premier dans votre classe de test. J'ai créé un bug avec ce problème: https://code.google.com/p/powermock/issues/detail?id=440

6
Marcin Stachniuk

Comme vous l'avez indiqué dans votre édition, vous pouvez ajouter la fonctionnalité dans le fichier enum lui-même. Cependant, cela pourrait ne pas être la meilleure option, car cela peut violer le principe de «responsabilité unique». Pour cela, vous pouvez également créer une carte statique contenant les valeurs enum en tant que clé et les fonctionnalités en tant que valeur. De cette façon, vous pouvez facilement vérifier si, pour toute valeur enum, vous avez un comportement valide en bouclant sur toutes les valeurs. Cet exemple est peut-être un peu compliqué, mais c’est une technique que j’utilise souvent pour mapper ids de ressources à enum

2
bert bruynooghe

Plutôt que d'utiliser des manipulations radicales de bytecode pour permettre à un test de toucher la dernière ligne de foo, je le supprime et j'appuie plutôt sur l'analyse de code statique. Par exemple, IntelliJ IDEA a l'inspection de code "Enum switch statement__", qui produira un cas d'erreur ", qui produirait un avertissement pour la méthode foo s'il manquait une case

2
Rogério

jMock (du moins à partir de la version 2.5.1 que j'utilise) peut le faire immédiatement. Vous devrez configurer votre Mockery pour utiliser ClassImposterizer.

Mockery mockery = new Mockery();
mockery.setImposterizer(ClassImposterizer.INSTANCE);
MyEnum unexpectedValue = mockery.mock(MyEnum.class);
1
Kevin Peterson

Tout d’abord, Mockito peut créer des données fictives pouvant être un entier long, etc. 

public enum HttpMethod {
      GET, POST, PUT, DELETE, HEAD, PATCH;
}

j'ai donc un total de 5 ordinal dans l'énumération HttpMethod mais mockito ne le sait pas. et obtenir un bon enum qui peut être passé pour un autre test 

import static org.mockito.Mockito.mock;

import Java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.internal.util.reflection.Whitebox;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.amazonaws.HttpMethod;




//@Test(expected = {"LoadableBuilderTestGroup"})
//@RunWith(PowerMockRunner.class)
public class testjava {
   // private static final Class HttpMethod.getClass() = null;
    private HttpMethod mockEnumerable;

    @Test
    public void setUpallpossible_value_of_enum () {
        for ( int i=0 ;i<10;i++){
            String name;
            mockEnumerable=    Matchers.any(HttpMethod.class);
            if(mockEnumerable!= null){
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());

                System.out.println(mockEnumerable.name()+"mocking suceess");
            }
            else {
                //Randomize all possible  value of  enum 
                Random Rand = new Random();
                int ordinal = Rand.nextInt(HttpMethod.values().length); 
                // 0-9. mockEnumerable=
                mockEnumerable= HttpMethod.values()[ordinal];
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());
            }
        }
    }







    @Test
    public void setUpallpossible_value_of_enumwithintany () {
        for ( int i=0 ;i<10;i++){
            String name;
            mockEnumerable=    Matchers.any(HttpMethod.class);
            if(mockEnumerable!= null){
                System.out.println(mockEnumerable.ordinal());
                System.out.println(mockEnumerable.name());

                System.out.println(mockEnumerable.name()+"mocking suceess");
            } else {
               int ordinal;
               //Randomize all possible  value of  enum 
               Random Rand = new Random();
               int imatch =  Matchers.anyInt();
               if(  imatch>HttpMethod.values().length)
                 ordinal = 0    ;
               else
                ordinal = Rand.nextInt(HttpMethod.values().length);

               // 0-9.  mockEnumerable=
               mockEnumerable= HttpMethod.values()[ordinal];
               System.out.println(mockEnumerable.ordinal());
               System.out.println(mockEnumerable.name());       
            }
       }  
    }
}

Sortie:

0
GET
0
GET
5
PATCH
5
PATCH
4
HEAD
5
PATCH
3
DELETE
0
GET
4
HEAD
2
PUT
0
Jin Thakur

Je pense que le moyen le plus simple pour atteindre l'exception IllegalArgumentException est de passer null à la méthode foo et vous lirez "Je ne sais pas comment gérer null"

0
Steven Kuypers