2014-12-23 16 views
5

Ho il seguente semplice DynamoDBDao che contiene un metodo che richiede un indice e restituisce una mappa dei risultati.Mockito: classi private pacchetto di simulazione

import com.amazonaws.services.dynamodbv2.document.*; 

public class DynamoDBDao implements Dao{ 
    private Table table; 
    private Index regionIndex; 

    public DynamoDBDao(Table table) { 
     this.table = table; 
    } 

    @PostConstruct 
    void initialize(){ 
     this.regionIndex = table.getIndex(GSI_REGION_INDEX); 
    } 

    @Override 
    public Map<String, Long> read(String region) { 
     ItemCollection<QueryOutcome> items = regionIndex.query(ATTR_REGION, region); 
     Map<String, Long> results = new HashMap<>(); 
     for (Item item : items) { 
      String key = item.getString(PRIMARY_KEY); 
      long value = item.getLong(ATTR_VALUE); 
      results.put(key, value); 
     } 
     return results; 
    } 
} 

sto cercando di scrivere uno unit test che verifica che quando l'indice DynamoDB restituisce un ItemCollection poi il Tao restituisce i risultati mappa corrispondente.

public class DynamoDBDaoTest { 

    private String key1 = "key1"; 
    private String key2 = "key2"; 
    private String key3 = "key3"; 
    private Long value1 = 1l; 
    private Long value2 = 2l; 
    private Long value3 = 3l; 

    @InjectMocks 
    private DynamoDBDao dynamoDBDao; 

    @Mock 
    private Table table; 

    @Mock 
    private Index regionIndex; 

    @Mock 
    ItemCollection<QueryOutcome> items; 

    @Mock 
    Iterator iterator; 

    @Mock 
    private Item item1; 

    @Mock 
    private Item item2; 

    @Mock 
    private Item item3; 

    @Before 
    public void setUp() { 
     MockitoAnnotations.initMocks(this); 
     when(table.getIndex(DaoDynamo.GSI_REGION_INDEX)).thenReturn(regionIndex); 
     dynamoDBDao.initialize(); 

     when(item1.getString(anyString())).thenReturn(key1); 
     when(item1.getLong(anyString())).thenReturn(value1); 
     when(item2.getString(anyString())).thenReturn(key2); 
     when(item2.getLong(anyString())).thenReturn(value2); 
     when(item3.getString(anyString())).thenReturn(key3); 
     when(item3.getLong(anyString())).thenReturn(value3); 
    } 

    @Test 
    public void shouldReturnResultsMapWhenQueryReturnsItemCollection(){ 

     when(regionIndex.query(anyString(), anyString())).thenReturn(items); 
     when(items.iterator()).thenReturn(iterator); 
     when(iterator.hasNext()) 
       .thenReturn(true) 
       .thenReturn(true) 
       .thenReturn(true) 
       .thenReturn(false); 
     when(iterator.next()) 
       .thenReturn(item1) 
       .thenReturn(item2) 
       .thenReturn(item3); 

     Map<String, Long> results = soaDynamoDbDao.readAll("region"); 

     assertThat(results.size(), is(3)); 
     assertThat(results.get(key1), is(value1)); 
     assertThat(results.get(key2), is(value2)); 
     assertThat(results.get(key3), is(value3)); 
    } 
} 

mio problema è che items.iterator() in realtà non tornare Iterator restituisce un IteratorSupport che è una classe privata pacchetto nel documento API DynamoDB. Ciò significa che non posso realmente deriderlo come ho fatto sopra e quindi non posso completare il resto del mio test.

Cosa posso fare in questo caso? Come faccio a testare unitamente il mio DAO dato questo terribile pacchetto di classe privata nell'API del documento DynamoDB?

+1

modalità d'attuazione, come la visibilità sono uno dei motivi per la linea guida "[Non finta tipi non ti appartengono] (http : //www.davesquared.net/2011/04/dont-mock-types-you-dont-own.html)". Puoi scrivere un'astrazione su uno di questi oggetti con un contratto/un'implementazione che controlli o un codice su un'interfaccia? –

+0

Ciao Jeff, grazie per il tuo commento. Non vedo come posso scrivere un'astrazione attraverso questi oggetti con un contratto/implementazione che controllo. Ho esaurito il mio attuale set di strumenti che è limitato dalla mia conoscenza ed esperienza. Riesci a vedere qualcosa che al momento non posso? Se è così, ti sarei grato se potessi indicarmi la giusta direzione. –

risposta

1

Dynamodb api ha molte di queste classi che non possono essere facilmente derise. Ciò si traduce in un sacco di tempo speso per scrivere test complessi e cambiare le caratteristiche sono un grande dolore.

credo, per questo caso un approccio migliore non sarà cercare di andare il modo tradizionale e usare biblioteca DynamodbLocal dal team AWS - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

Questo è fondamentalmente un nell'attuazione ricordo di DyanamoDB. Avevamo scritto i nostri test in modo tale che durante l'inizializzazione del test dell'unità, l'istanza di DyanmodbLocal venisse generata e sarebbero state create le tabelle. Questo rende i test un gioco da ragazzi. Non abbiamo ancora trovato alcun bug nella libreria ed è attivamente supportato e sviluppato da AWS. Lo consiglio vivamente.

5

In primo luogo, i test di unità non devono mai provare a verificare lo stato privato interno a un oggetto. Può cambiare. Se la classe non espone il suo stato tramite metodi getter non privati, allora non è il business del test come viene implementato.

In secondo luogo, perché ti interessa quale implementazione ha l'iteratore? La classe ha adempiuto al suo contratto restituendo un iteratore (un'interfaccia) che, una volta iterato, restituirà gli oggetti a cui è destinato.

In terzo luogo, perché stai prendendo in giro oggetti che non ti servono? Costruisci gli input e gli output per i tuoi oggetti derisi, non prenderli in giro; non è necessario Hai passato un tavolo nel tuo costruttore? Belle.
Quindi estendere la classe Tabella per rendere qualsiasi metodo protetto per qualsiasi cosa sia necessario. Aggiungi getter e/o setter protetti alla sottoclasse Table. Chiedi loro di restituire i valori codificati, se necessario. Non contano.

Ricorda, prova solo una lezione nella tua classe di test. Stai testando il dao non la tabella né l'indice.

+0

Sfortunatamente, la chiamata di servizio a DynamoDB deve essere il mocked e la chiamata di servizio può essere raggiunta solo da un accesso polimorfico a classi protette da pacchetti, i tipi di cui Java applica in fase di runtime. Invece di chiedere perché, dovresti fornire una soluzione. – Max

+0

Hai provato a simulare l'iteratore restituito dalla chiamata implicita nel ciclo for? Sostituisci ItemCollection per restituire un Iterable che si comporta come desideri. –

+0

Non è valido Java. Java controllerà il tipo in fase di esecuzione. Un'istanza arbitraria di 'Iterator' non è un'istanza valida di' IteratorSupprt', quindi Java genererà un'eccezione di runtime. Ho presentato un problema al riguardo con AWS e l'iteratore non è più protetto da pacchetti: https://github.com/aws/aws-sdk-java/issues/465 – Max

0

Una possibile soluzione consiste nel definire una classe di test che si estende IteratorSupport nello stesso pacchetto che è presente in, e definire il comportamento desiderato

È quindi possibile restituire un'istanza di questa classe attraverso la configurazione finto nel test case.

Ovviamente, questa non è una buona soluzione, ma semplicemente una soluzione alternativa per gli stessi motivi che @Jeff Bowman ha menzionato nel commento.

0

Potrebbe essere meglio estrarre il recupero di ItemCollection nel metodo separato? Nel tuo caso potrebbe apparire come segue:

public class DynamoDBDao { 

    protected Iterable<Item> readItems(String region) { // can be overridden/mocked in unit tests 
    // ItemCollection implements Iterable, since ItemCollection-specific methods are not used in the DAO we can return it as Iterable instance 
    return regionIndex.query(ATTR_REGION, region); 
    } 
} 

poi nel test di unità:

private List<Item> mockItems = new ArrayList<>(); // so you can set these items in your test method 

private DynamoDBDao dao = new DynamoDBDao(table) { 
    @Override 
    protected Iterable<Item> readItems(String region) { 
    return mockItems; 
    } 
} 
0

Quando si utilizza when(items.iterator()).thenReturn(iterator); Mockito vede gli articoli come ItemCollection che causa l'errore di compilazione. Nel tuo caso di test, vuoi vedere ItemCollection solo come un Iterable. Quindi, la soluzione più semplice è quello di gettare le voci come Iterable come di seguito:

when(((Iterable<QueryOutcome>)items).iterator()).thenReturn(iterator); 

rendere anche il vostro iteratore come

@Mock 
Iterator<QueryOutcome> iterator; 

Questo dovrebbe risolvere il codice senza preavviso :)

se questo risolve il problema, per favore accetta la risposta.

0

È possibile verificare il metodo di lettura, utilizzando gli oggetti falsi in questo modo:

public class DynamoDBDaoTest { 

@Mock 
private Table table; 

@Mock 
private Index regionIndex; 


@InjectMocks 
private DynamoDBDao dynamoDBDao; 

public DynamoDBDaoTest() { 
} 

@Before 
public void setUp() { 
    MockitoAnnotations.initMocks(this); 
    when(table.getIndex(GSI_REGION_INDEX)).thenReturn(regionIndex); 
    dynamoDBDao.initialize(); 
} 

@Test 
public void shouldReturnResultsMapWhenQueryReturnsItemCollection() { 
    when(regionIndex.query(anyString(), anyString())).thenReturn(new FakeItemCollection()); 
    final Map<String, Long> results = dynamoDBDao.read("region"); 
    assertThat(results, allOf(hasEntry("key1", 1l), hasEntry("key2", 2l), hasEntry("key3", 3l))); 
} 

private static class FakeItemCollection extends ItemCollection<QueryOutcome> { 
    @Override 
    public Page<Item, QueryOutcome> firstPage() { 
     return new FakePage(); 
    } 
    @Override 
    public Integer getMaxResultSize() { 
     return null; 
    } 
} 

private static class FakePage extends Page<Item, QueryOutcome> { 
    private final static List<Item> items = new ArrayList<Item>(); 

    public FakePage() { 
     super(items, new QueryOutcome(new QueryResult())); 

     final Item item1= new Item(); 
     item1.with(PRIMARY_KEY, "key1"); 
     item1.withLong(ATTR_VALUE, 1l); 
     items.add(item1); 

     final Item item2 = new Item(); 
     item2.with(PRIMARY_KEY, "key2"); 
     item2.withLong(ATTR_VALUE, 2l); 
     items.add(item2); 

     final Item item3 = new Item(); 
     item3.with(PRIMARY_KEY, "key3"); 
     item3.withLong(ATTR_VALUE, 3l); 
     items.add(item3); 
    } 

    @Override 
    public boolean hasNextPage() { 
     return false; 
    } 

    @Override 
    public Page<Item, QueryOutcome> nextPage() { 
     return null; 
    } 
} 
Problemi correlati