2015-07-20 8 views
8

Ho un test di unità che deve controllare un valore di mappa annidato. Posso far funzionare la mia affermazione estraendo la voce e facendo corrispondere il sottostante Map, ma stavo cercando un modo chiaro per mostrare cosa sta facendo l'affermazione. Ecco un test molto semplificato:Come affermare Mappa contiene Mappa con voce

import static org.hamcrest.MatcherAssert.assertThat; 
import static org.hamcrest.Matchers.hasEntry; 

import java.util.HashMap; 
import java.util.Map; 

import org.junit.Test; 

public class MapContainsMapTest { 
    @Test 
    public void testMapHasMap() { 
     Map<String, Object> outerMap = new HashMap<String, Object>(); 
     Map<String, Object> nestedMap = new HashMap<String, Object>(); 
     nestedMap.put("foo", "bar"); 
     outerMap.put("nested", nestedMap); 

     // works but murky 
     assertThat((Map<String, Object>) outerMap.get("nested"), hasEntry("foo", "bar")); 
     // fails but clear 
     assertThat(outerMap, hasEntry("nested", hasEntry("foo", "bar"))); 
    } 
} 

sembra che il problema è la mappa esterna viene confrontato con hasEntry(K key, V value) mentre ciò che voglio usare è hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher). Non sono sicuro di come forzare l'asserzione per usare il secondo modulo.

Grazie in anticipo.

+0

"_It sembra che il problema è la mappa esterna viene confrontato con ..._" 'hasEntry (è ("chiave"), è ("valore"))' per esempio potrebbe causare il secondo modulo da utilizzare. Forse potresti usare il matcher 'equalTo' al posto di' is', ma in entrambi i casi, la leggibilità esce dalla finestra. – mystarrocks

+0

Il problema è con il test esterno, quindi ho bisogno di qualcosa come 'equalTo (" nested ") ...' ma ciò non funziona a meno che non cambi il tipo di mappa esterna in 'Map > ' –

risposta

2

avrei probabilmente estendere una nuova Matcher per questo, qualcosa di simile (attenzione, NPE in agguato):

class SubMapMatcher extends BaseMatcher<Map<?,?>> { 

private Object key; 
private Object subMapKey; 
private Object subMapValue; 

public SubMapMatcher(Object key, Object subMapKey, Object subMapValue) { 
    super(); 
    this.key = key; 
    this.subMapKey = subMapKey; 
    this.subMapValue = subMapValue; 
} 

@Override 
public boolean matches(Object item) { 

    Map<?,?> map = (Map<?,?>)item; 

    if (!map.containsKey(key)) { 
     return false; 
    } 

    Object o = map.get(key); 

    if (!(o instanceof Map<?,?>)) { 
     return false; 
    } 

    Map<?,?> subMap = (Map<?,?>)o; 
    return subMap.containsKey(subMapKey) && subMap.get(subMapKey).equals(subMapValue); 
} 

@Override 
public void describeTo(Description description) { 
    description.appendText(String.format("contains %s -> %s : %s", key, subMapKey, subMapValue)); 
} 

public static SubMapMatcher containsSubMapWithKeyValue(String key, String subMapKey, String subMapValue) { 
    return new SubMapMatcher(key, subMapKey, subMapValue); 
} 

} 
+0

Grazie, questa risposta funziona. Tuttavia, penso che mi piacerebbe vedere se posso estenderlo per consentire qualsiasi livello di nidificazione. –

+0

Sembra abbastanza facile, puoi usare una firma come "Valore dell'oggetto, String ... chiavi". Quindi scorrere le chiavi, ottenere il submap fino a raggiungere l'ultimo (o inciampare su un valore null o non-map come valore) e controllare il valore. Ad esempio 'containsValueInSubMap (someValue, map0Key, map1Key, map2Key);' Naturalmente, personalmente NON suggerirei di utilizzare una mappatura standard per una chiave -> "valore OR submap", ma invece qualcosa di più specifico che consentirebbe anche di avere un generico tipi di valore (una mappa con submap e valori è effettivamente un albero, dovrebbe essere facile trovare qualcosa). –

2

se si dichiara outerMap come Map<String, Map<String, Object>> non è necessario il brutto cast. Come questo:

public class MapContainsMapTest { 

    @Test 
    public void testMapHasMap() { 
     Map<String, Map<String, Object>> outerMap = new HashMap<>(); 
     Map<String, Object> nestedMap = new HashMap<>(); 
     nestedMap.put("foo", "bar"); 
     outerMap.put("nested", nestedMap); 

     assertThat(outerMap.get("nested"), hasEntry("foo", "bar")); 
    } 
} 
+0

@DanGetz se rimuovo il cast, esso non compila, outerMap deve essere dichiarato come Map >. –

+0

@DanGetz meglio? –

+0

Il mio esempio è una versione semplificata del mio test in cui outerMap è un oggetto JSON in cui non tutte le voci sono mappe, quindi devo mantenere la dichiarazione della mappa come scritta. –

2

Se solo si vuole mettere Map<String, Object> come valori nel outerMap regolare la dichiarazione di conseguenza. Quindi è possibile fare

@Test 
public void testMapHasMap() { 
    Map<String, Map<String, Object>> outerMap = new HashMap<>(); 
    Map<String, Object> nestedMap = new HashMap<String, Object>(); 
    nestedMap.put("foo", "bar"); 
    outerMap.put("nested", nestedMap); 

    Object value = "bar"; 
    assertThat(outerMap, hasEntry(equalTo("nested"), hasEntry("foo", value))); 
} 

Object value = "bar"; è necessario per motivi di compilazione. In alternativa è possibile utilizzare

assertThat(outerMap, 
    hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar"))); 
+0

Il mio esempio è una versione semplificata del mio test in cui outerMap è un oggetto JSON in cui non tutte le voci sono mappe, quindi devo mantenere la dichiarazione della mappa come scritta. –

Problemi correlati