Test unitaire pour la valeur Enum qui n’existe pas?

Quelques exemples de code en premier …

L’énumération:

public enum TestEnum { YES, NO } 

Un code:

 public static boolean WorkTheEnum(TestEnum theEnum) { switch (theEnum) { case YES: return true; case NO: return false; default: // throws an exception here } } 

Problème:
TestEnum est quelque chose que j’importe d’importer à partir d’un code différent d’un développeur différent. Cela pourrait donc changer. Pour ce cas, je veux avoir un test unitaire qui vérifie réellement cette valeur non existante. Mais je ne sais tout simplement pas comment faire cela avec Mockito et JUnit.

Cette partie ne fonctionne bien sûr pas:

 @Test(expected=Exception.class) public void DoesNotExist_throwsException() throws Exception { when(TestEnum.MAYBE).thenReturn(TestEnum.MAYBE); WorkTheEnum(TestEnum.MAYBE); } 

J’ai trouvé un exemple qui utilise PowerMock, mais je ne pouvais pas le faire fonctionner avec Mockito.

Des idées?

Que diriez-vous d’un simple:

 Set expected = new HashSet<> (Arrays.asList("YES", "NO")); Set actual = new HashSet<>(); for (TestEnum e : TestEnum.values()) actual.add(e.name()); assertEquals(expected, actual); 

(en utilisant HashSet plutôt que ArrayList car l’ordre n’a pas d’importance)

En me basant sur la réponse de @assylias, je pense que c’est le mieux que vous puissiez faire:

 List unknown = new ArrayList<>(); for (TestEnum e : TestEnum.values()) unknown.add(e.name()); unknown.removeAll(Arrays.asList("YES", "NO")); if (unknown.isEmpty()) { // Not possible to reach default case, do whatever you need to do } else { TestEnum notIncluded = TestEnum.valueOf(unknown.get(0)); workTheEnum(notIncluded); } 

Il est impossible (autant que je sache) de simuler une valeur enum non existante dans une instruction switch , en raison de la façon dont les instructions enum switch sont compilées. Même si vous avez l’ ArrayIndexOutOfBoundsException champ ordinal interne dans l’instance enum via reflection, l’instruction switch donnera une ArrayIndexOutOfBoundsException plutôt que de passer au cas default .


Voici un code qui semble pouvoir fonctionner, mais ne fonctionne pas, en raison de l’ ArrayIndexOutOfBoundsException mentionnée ci-dessus:

 TestEnum abused = TestEnum.YES; try { Class c = abused.getClass().getSuperclass(); Field[] declaredFields = c.getDeclaredFields(); Field ordinalField = null; for (Field e : declaredFields) { if (e.getName().equals("ordinal")) { ordinalField = e; } } ordinalField.setAccessible(true); ordinalField.setInt(abused, TestEnum.values().length); workTheEnum(abused); } catch (Exception e) { e.printStackTrace(System.err); } 

OK, voici quelque chose qui pourrait fonctionner pour vous. C’est assez difficile, donc pour moi c’est probablement pire que de ne pas avoir une couverture de code à 100%, YMMV. Cela fonctionne en remplaçant les tableaux de recherche ordinaux enum par des tableaux contenant tous les zéros, ce qui correspond au cas par défaut.

 // Setup values - needs to be called so that // $SWITCH_TABLE$FooClass$BarEnum is initialised. workTheEnum(TestEnum.YES); workTheEnum(TestEnum.NO); // This is the class with the switch statement in it. Class c = ClassWithSwitchStatement.class; // Find and change fields. Map changedFields = new HashMap<>(); Field[] declaredFields = c.getDeclaredFields(); try { for (Field f : declaredFields) { if (f.getName().startsWith("$SWITCH_TABLE$")) { f.setAccessible(true); int[] table = (int[])f.get(null); f.set(null, new int[table.length]); changedFields.put(f, table); } } workTheEnum(TestEnum.YES); } finally { for (Map.Entry entry : changedFields.entrySet()) { try { entry.getKey().set(null, entry.getValue()); } catch (Exception ex) { ex.printStackTrace(System.err); } } } 

Mockito ne supporte pas les moqueries des valeurs enum, mais powermock

Essaye ça.

J’ai créé mes propres classes pour les simuler. S’il vous plaît mappez à vos propres classes.

 @RunWith(PowerMockRunner.class) @PrepareForTest(Trail.class) public class TrailTest { @Before public void setUp() { Trail mockTrail = PowerMock.createMock(Trail.class); Whitebox.setInternalState(mockTrail, "name", "Default"); Whitebox.setInternalState(mockTrail, "ordinal", 2); PowerMock.mockStatic(Trail.class); expect(Trail.values()).andReturn(new Trail[]{Trail.YES, Trail.NO, mockTrail}); expect(Trail.valueOf("default value")).andReturn(mockTrail); PowerMock.replay(Trail.class); } @Test(expected = RuntimeException.class) public void test() { Trail aDefault = Trail.valueOf("default value"); BasicTrails.find(aDefault); } } 

C’est la méthode:

 public class BasicTrails { public static boolean find(Trail trail) { switch (trail) { case YES: return true; case NO: return false; default: throw new RuntimeException("Invalid"); } } 

C’est l’énum

 public enum Trail { YES, NO; } 

Avec l’aide de Powermock, nous pouvons y parvenir, car Powermock encourage les moqueries des dernières classes.

 import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.mockito.Mock; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(Trail.class) public class TrailTest { @Mock Trail mockTrail; @Before public void setUp() { PowerMockito.mockStatic(Trail.class); BDDMockito.given(Trail.values()).willReturn(new Trail[]{Trail.YES, Trail.NO, mockTrail}); BDDMockito.given(Trail.valueOf("YES")).willReturn(mockTrail.YES); BDDMockito.given(Trail.valueOf("NO")).willReturn(mockTrail.NO); } @Test public void test() { assertTrue(BasicTrails.find(mockTrail.valueOf("YES"))); assertFalse(BasicTrails.find(mockTrail.valueOf("NO"))); try{ Trail aDefault = mockTrail.valueOf("default value"); }catch (Exception e) { System.out.println(e); } } } 

Vous pouvez vous moquer, bidouiller ou essayer de le faire fonctionner, mais il existe un moyen assez simple de procéder. Je suppose que vous travaillez avec maven ou gradle, vous avez donc test profils main et de test .

Ensuite, dans le profil principal, vous avez le code ci-dessus:

 package my.cool.package; public enum TestEnum { YES, NO } 

mais dans le profil de test, vous pouvez en avoir un autre:

 // EXACTLY SAME as above package my.cool.package; public enum TestEnum { YES, NO, INVALID_FOR_TEST_ONLY } 

et maintenant vous pouvez utiliser la nouvelle valeur INVALID_FOR_TEST_ONLY dans le test et elle ne sera pas disponible dans le profil prod.

Il y a deux inconvénients:

  • si vous mettez à jour prod enum, vous devrez peut-être mettre à jour test également (si vous voulez tester ensuite)
  • certains IDE peuvent ne pas fonctionner correctement avec cette astuce, même Maven le comprend bien