Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito: Mocking package private classes

I have the following simple DynamoDBDao which 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 which verifies that when the DynamoDB index returns an ItemCollection then the Dao returns the corresponding results 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 an IteratorSupport which is a package private class in the DynamoDB document API. This means that I cannot actually mock it as I did above and so I cannot complete the rest of my test.

What can I do in this case? How do I unit test my DAO correctly given this awful package private class in the DynamoDB document API?

like image 852
Cathal Coffey Avatar asked Dec 23 '14 08:12

Cathal Coffey


2 Answers

First, unit tests should never try to verify private state internal to an object. It can change. If the class does not expose its state via non-private getter methods, then it is none of your test's business how it is implemented.

Second, why do you care what implementation the iterator has? The class has fulfilled its contract by returning an iterator (an interface) which when iterated over will return the objects it is supposed to.

Third, why are you mocking objects that you don't need to? Construct the inputs and outputs for your mocked objects, don't mock them; it is unnecessary. You pass a Table into your constructor? Fine.
Then extend the Table class to make whatever protected methods for whatever you need. Add protected getters and/or setters to your Table subclass. Have them return hard coded values if necessary. They don't matter.

Remember, only test one class in your test class. You are testing the dao not the table nor the index.

like image 186
Rick Ryker Avatar answered Oct 10 '22 10:10

Rick Ryker


Dynamodb api has a lot of such classes which can not easily be mocked. This results in lot of time spent on writing complex tests and changing features are big pain.

I think, for this case a better approach will be not try to go the traditional way and use DynamodbLocal library by the AWS team - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

This is basically an in memory implementation of DyanamoDB. We had written our tests such that during unit test initialization, DyanmodbLocal instance would be spawned and tables would be created. This makes the testing a breeze. We have not yet found any bugs in the library and it is actively supported and developed by AWS. Highly recommend it.

like image 36
Nilanjan Basu Avatar answered Oct 10 '22 11:10

Nilanjan Basu