How to mock DynamoDB's ItemCollection<QueryResult> using EasyMock?

8.8k views Asked by At

I have the following Java code:

Index userNameIndex = userTable.getIndex("userNameIndex");
ItemCollection<QueryOutcome> userItems = userNameIndex.query("userName", userName);

for (Item userItem : userItems) {
}

I am trying to write a unit test and I would like to mock the ItemCollection<QueryOutcome>. The issue is that the iterator returned by ItemCollection<QueryOutcome>::iterator is of type IteratorSupport, which is a package protected class. Therefore, it is impossible to mock the return type of this iterator. What can I do instead?

Thanks!

2

There are 2 answers

1
Rohit Kulshreshtha On BEST ANSWER

This may not be the best way to do it, but it works and may require you to change the way you get the iterator in the class under test.

@Test
public void doStuff() throws ClassNotFoundException {

    Index mockIndex;
    ItemCollection<String> mockItemCollection;
    Item mockItem = new Item().with("attributeName", "Hello World");

    mockItemCollection = EasyMock.createMock(ItemCollection.class);

    Class<?> itemSupportClasss = Class.forName("com.amazonaws.services.dynamodbv2.document.internal.IteratorSupport");
    Iterator<Item> mockIterator = (Iterator<Item>) EasyMock.createMock(itemSupportClasss);

    EasyMock.expect(((Iterable)mockItemCollection).iterator()).andReturn(mockIterator);     
    EasyMock.expect(mockIterator.hasNext()).andReturn(true);
    EasyMock.expect(mockIterator.next()).andReturn(mockItem);
    EasyMock.replay(mockItemCollection, mockIterator);

    /* Need to cast item collection into an Iterable<T> in 
       class under test, prior to calling iterator. */
    Iterator<Item> Y = ((Iterable)mockItemCollection).iterator();
    Assert.assertSame(mockItem, Y.next());

}
3
Henri On

The previous answer is valid. However, if you can mock Iterable instead of ItemCollection, your life will be easier.

    Iterable<Item> mockItemCollection = createMock(Iterable.class);
    Iterator<Item> mockIterator = createMock(Iterator.class);

    Item mockItem = new Item().with("attributeName", "Hello World");

    expect(mockItemCollection.iterator()).andReturn(mockIterator);
    expect(mockIterator.hasNext()).andReturn(true).andReturn(false);
    expect(mockIterator.next()).andReturn(mockItem);

    replay(mockItemCollection, mockIterator);

    for(Item i : mockItemCollection) {
        assertSame(i, mockItem);
    }

    verify(mockItemCollection, mockIterator);

BTW, I'm a big fan of static imports at least in the test code. It makes it more readable.

Reading the AWS code, I would consider their code to have a design flaw. It doesn't make sense to return a package scope class from a public interface. It's probably something that should be raised as an issue to them.

You could also always wrap the ItemCollection into a correctly typed class:

public class ItemCollectionWrapper<R> implements Iterable<Item> {

    private ItemCollection<R> wrapped;

    public ItemCollectionWrapper(ItemCollection<R> wrapped) {
        this.wrapped = wrapped;
    }

    public Iterator<Item> iterator() {
        return wrapped.iterator();
    }
}