2014-09-19 14 views
12

Niektóre przykład pierwszy kod ...Test jednostkowy dla wartości wyliczeniowej, która nie istnieje?

wyliczenia:

public enum TestEnum { 
    YES, 
    NO 
} 

Niektóre kodu:

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

Problem:
TestEnum jest coś importować z innego kodu inny programista. Tak naprawdę to może się zmienić. W tym przypadku chcę mieć test jednostkowy, który faktycznie sprawdza tę nieistniejącą wartość. Ale po prostu nie wiem jak to zrobić z Mockito i JUnit.

Ta część jest oczywiście nie działa:

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

znalazłem jeden przykład, że usees PowerMock, ale nie mogę zmusić go do pracy z Mockito.

Wszelkie pomysły?

+0

Nie jestem wystarczająco pewny siebie, twierdząc, że to jest odpowiedź, ale: Czy nie jest możliwe zrobienie 'TestEnum notAValidEnum = Mockito.mock (TestEnum.class);', a następnie podanie to do 'WorkTheEnum()' ...? – kinbiko

+0

Prawdopodobny duplikat: https://stackoverflow.com/questions/5323505 – augurar

Odpowiedz

6

Jak o prosty:

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

(przy użyciu HashSet zamiast ArrayList ponieważ kolejność nie ma znaczenia)

+0

Ale to by przetestowało tylko zawartość enum, a nie domyślną ścieżkę instrukcji switch. – Feroc

+0

Rozumiem - chciałbyś się upewnić, że metoda zgłasza wyjątek, jeśli domyślny przypadek został osiągnięty, więc chciałbyś wyśmiać wyliczenie, aby zasymulować nieoczekiwaną wartość. Czy to prawda? – assylias

+0

To jest dokładnie to, co chcę zrobić, tak. – Feroc

5

budynek na odpowiedź od @assylias, myślę, że to jest najlepsze, co możesz zrobić:

List<String> 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); 
} 

to nie jest możliwe (AFAIK) do fałszywej nieistniejącej wartości enum w switch stwierdzeniem, ze względu na sposób, że enum Instrukcje switch są kompilowane. Nawet jeśli użyjesz wewnętrznego pola ordinal w instancji enum za pomocą odbicia, instrukcja switch da raczej ArrayIndexOutOfBoundsException niż przechodzenie do obudowy default.


Oto kod, który wygląda jak to może działać, ale nie ze względu na ArrayIndexOutOfBoundsException mowa powyżej:

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, tutaj jest coś, co może pracować dla Ciebie . To jest dość hacky, więc dla mnie jest to prawdopodobnie gorsze niż brak 100% pokrycia kodu, YMMV. Działa poprzez zastąpienie tablic wyliczeniowych porządkowych wyliczeniowych z tablicami zawierającymi wszystkie zera, które przenikają do domyślnego przypadku.

// 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<Field, int[]> 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<Field, int[]> entry : changedFields.entrySet()) { 
    try { 
     entry.getKey().set(null, entry.getValue()); 
    } catch (Exception ex) { 
     ex.printStackTrace(System.err); 
    } 
    } 
} 
+0

Może kpiny nie są poprawne? Ale musi istnieć sposób użycia refleksji, aby dodać wartość, aby wyliczenie było rzeczywiście poprawne i ma wszystkie te wartości !? – Feroc

+0

Nie, nie sądzę. Podczas kompilacji instrukcji switch generowana jest tablica zawierająca wszystkie liczby porządkowe wyliczonych wartości istniejących w tym momencie. Jeśli spróbujesz zmodyfikować istniejące wartości, aby zmienić ich numery porządkowe, kod generowany przez kompilator będzie uzyskiwał dostęp do wartości spoza granic tablicy i kończy się niepowodzeniem z 'ArrayIndexOutOfBoundsException'. Załączam kod, który to robi, żebyś mógł zobaczyć, czy chcesz. – msandiford

+0

Dzięki za szczegółową odpowiedź @msandiford. Pech, jak sądzę. – Feroc

2

Mockito nie obsługuje wyśmianie wartości enum ale powermock robi.

Spróbuj tego.

Stworzyłem własne klasy, aby je zasymulować. Proszę odwzorować na własne klasy.

@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); 
    } 
} 

Jest to metoda:

public class BasicTrails { 

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

Jest to enum

public enum Trail { 
    YES, NO; 
} 
+0

Dzięki za pomoc.Nie jestem pewien, czy możemy użyć Powermock tutaj. – Feroc

+0

To tylko kolejna biblioteka 'mock'. To zależy od wyboru. – VinayVeluri

+0

możesz poprawić odpowiedź, jeśli skonwertujesz swój kod w sposób zgodny z pytaniem OP – gstackoverflow

1

Z pomocą Powermock możemy osiągnąć to jako Powermock obsługuje wyśmianie klas końcowych

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); 
     } 


    } 
} 
0

Możesz udawać, ha ck lub próbując sprawić, żeby działało, ale jest dość prosty sposób, jak to zrobić. Zakładam, że pracujesz z maven lub gradle, więc masz profile main i test.

Następnie w głównym profilu masz kod jak wyżej:

package my.cool.package; 

public enum TestEnum { 
    YES, 
    NO 
} 

ale potem w profilu testowym może masz inny jedno:

// EXACTLY SAME as above 
package my.cool.package; 

public enum TestEnum { 
    YES, 
    NO, 
    INVALID_FOR_TEST_ONLY 
} 

i teraz można używać nowej wartości INVALID_FOR_TEST_ONLY w test i nie będzie dostępny w profilu prod.

Istnieją dwie wady:

  • jeśli aktualizować prod enum może trzeba zaktualizować testu, a także (jeśli chcesz testu potem)
  • niektóre IDE może nie działać z tej sztuczki poprawnie nawet maven dobrze to rozumie
Powiązane problemy