5

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!

Max
  • 15,157
  • 17
  • 82
  • 127

2 Answers2

2

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();
    }
}
Henri
  • 5,551
  • 1
  • 22
  • 29
  • Your answer is very nice, but, of course, the ItemCollection is what is returned from the Dynamo client so it's hard to get around using it. – Max Jul 16 '15 at 19:45
  • Yes. I know. You can also wrap Index to have it return a real ItemCollection instead of the package scope class. – Henri Jul 18 '15 at 19:01
  • Great answer! I've implemented your solution successfully in Kotlin using the Mockk library. Thx Bro! – Anderson Marques Sep 05 '18 at 22:57
1

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

}
  • Just wanted to note that `IteratorSupport` is no longer packaged protected so it can be mocked directly. – Max Mar 19 '16 at 14:24