Mockito: Hidden Private Class Package

I have the following simple DynamoDBDao that contains one method that queries an index and returns a map of results.

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

I am trying to write a unit test that checks that when the DynamoDB index returns an ItemCollection, then Tao returns the corresponding result map.

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

My problem is that items.iterator () does not actually return Iterator, it returns IteratorSupport, which is a private package class in the DynamoDB document API. This means that I cannot actually make fun of it, as I did above, and therefore I cannot complete the rest of my test.

What can I do in this case? How do I unit test my DAO correctly give this terrible batch class in the DynamoDB document API?

+5
source share
6 answers

First, unit tests should never attempt to verify the personal state inside an object. This may change. If a class does not reveal its state using non-implicit getter methods, then this is not your test business, as it is implemented.

Secondly, why do you care about which implementation has an iterator? The class fulfilled its contract by returning an iterator (interface) which, when repeated, will return the objects that it should use.

Third, why are you mocking items you don't need? Build entrances and exits for your mocking objects, do not mock them; it's not needed. Do you pass the table to your constructor? Fine
Then extend the Table class to make all protected methods for everything you need . Add protected getters and / or setters to the Table subclass. Ask them to return hard-coded values ​​if necessary. They do not matter.

Remember, test only one class in a test class. You are testing dao not a table, but an index.

+5
source

Dynamodb api has many such classes that are not easy to mock. This leads to a lot of time spent on writing complex tests, and changing functions is a big pain.

I think for this case the best approach will not go the traditional way and use the DynamodbLocal library with the AWS command - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

This is basically an implementation of DyanamoDB memory. We wrote our tests in such a way that during the unit test initialization, an instance of DyanmodbLocal will be created and tables will be created. This makes testing easy. We have not found any errors in the library yet and we actively support and develop AWS. Highly recommend.

+1
source

A possible workaround is to define a test class that extends IteratorSupport in the same package in which it is present and defines the desired behavior

Then you can return an instance of this class using the mock setup in the test case.

Of course, this is not a good solution, but just a workaround for the same reasons that @Jeff Bowman mentioned in the comment.

0
source

Maybe it is better to extract ItemCollection extracts in a separate method? In your case, it might look like this:

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

then in unit tests:

 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
source

When using when(items.iterator()).thenReturn(iterator); Mockito sees items as ItemCollection, which cause a compilation error. In your test case, you want ItemCollection to be just Iterable. So, a simple solution is to distinguish the elements as Iterable, as shown below:

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

Also make your iterator

 @Mock Iterator<QueryOutcome> iterator; 

This should fix the code without warning :)

If this fixes the problem, please accept the answer.

0
source

You can test your reading method using fake objects like this:

 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; } } 
0
source

Source: https://habr.com/ru/post/1209756/


All Articles