Hot questions for Using Mockito in amazon dynamodb

Top 10 Java Open Source / Mockito / amazon dynamodb

Question:

I have a section of dynamoDb related code that I want to test using mockito.

The method I want to test contains the following line:

List<NotificationFeedRecord> listResult = mapper.query(NotificationFeedRecord.class, queryExpression);

It's work fine when I test by hand, I submit a query and get the expected results back from dynamodb.

I'm writing unit tests and want to mock mapper.query.

I have:

mapper = mock(DynamoDBMapper.class);
List<NotificationFeedRecord> testList = new ArrayList<>();
when(mapper.query(any(), any())).thenReturn(testList);

Here I get an error

Error:(133, 37) java: no suitable method found for thenReturn(java.util.List<notificationfeed.lib.db.NotificationFeedRecord>)
      (argument mismatch; java.util.List<notificationfeed.lib.db.NotificationFeedRecord> cannot be converted to com.amazonaws.services.dynamodbv2.datamodeling.PaginatedQueryList<java.lang.Object>)

I've tried a range of fixes (e.g. Creating PaginatedQueryList and returning that, changing the query matchers), but all given an error.

The .query method is declared as follows:

public <T> PaginatedQueryList<T> query(Class<T> clazz, DynamoDBQueryExpression<T> queryExpression) {
        return query(clazz, queryExpression, config);
    }

How does one mock mapper.query? Is there something special about it?


Answer:

It was simple really, I had to mock the PaginatedQueryList and then:

when(mapper.query(any(), anyObject())).thenReturn(paginatedQueryList)

;

That worked for me.

These are all the conditions I set-up for our tests:

@Mock private PaginatedQueryList paginatedQueryList;
doReturn(mockQuery).when(utilSpy).getQueryExpression();

when(mockQuery.withFilterExpression(anyString())).thenReturn(mockQuery);
when(mockQuery.withLimit(anyInt())).thenReturn(mockQuery);
when(mockQuery.withExpressionAttributeValues(anyMap())).thenReturn(mockQuery);
when(mockQuery.withIndexName(anyString())).thenReturn(mockQuery);
when(mockQuery.withHashKeyValues(anyString())).thenReturn(mockQuery);
when(mockQuery.withConsistentRead(anyBoolean())).thenReturn(mockQuery); 
when(mockQuery.withRangeKeyCondition(anyString(), anyObject())).thenReturn(mockQuery);
when(mapper.query(any(), anyObject())).thenReturn(paginatedQueryList);

Question:

Consider DynamoDB's QueryApi. Through a series of (unfortunate?) hoops,

ItemCollection<QueryOutcome>>

ends up being equivalent to

Iterable<Item>

I know this because I can do:

public PuppyDog getPuppy(final String personGuid, final String name) {
    final QuerySpec spec = new QuerySpec()
            .withKeyConditionExpression("#d = :guid and #n = :name")
            .withNameMap(new NameMap().with("#d", "guid").with("#n", "name"))
            .withValueMap(new ValueMap().withString(":guid", personGuid).withString(":name", name));
    return getDog(index.query(spec));
}

private PuppyDog getDog(final Iterable<Item> itemCollection) {
    // http://stackoverflow.com/questions/23932061/convert-iterable-to-stream-using-java-8-jdk
    return StreamSupport.stream(itemCollection.spliterator(), false)
    .map(this::createDogFor)
    // it would be a little weird to find more than 1, but not sure what to do if so.
    .findAny().orElse(new PuppyDog());
}

But when I try to write tests in Mockito using BDDMockito:

@Test
public void canGetPuppyDogByPersonGuidAndName() {
    final PuppyDog dawg = getPuppyDog();
    final ArgumentCaptor<QuerySpec> captor = ArgumentCaptor.forClass(QuerySpec.class);
    final ItemCollection<QueryOutcome> items = mock(ItemCollection.class);
    given(query.query(captor.capture())).willReturn(items);
}

The compiler complains when I try to make items an Iterable.

Why dis?


Answer:

Dis not because of BDDMockito. Dis because ItemCollection<QueryOutcome> simply can't be cast safely into Iterable<Item>. It can be cast into Iterable<QueryOutcome> or even Iterable<? extends Item>, but not Iterable<Item>.

Otherwise you could do this:

final ItemCollection<QueryOutcome> items = mock(ItemCollection.class);
Collection<Item> yourItemCollection = items;
yourItemCollection.add(itemThatIsNotAQueryOutcome);  // violating safety of items

See also:

  • Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?
  • Why are arrays covariant but generics are invariant?

Question:

@Component
class ClassA{

    @Autowired
    ClassB classB;
     public void doSomething(){
         classD.createValues(a,b);
         //create values calls ClassB method
     }
}

@Component
class ClassB{

    @Autowired
    DynamoDBMapper mapper;

    public void doSomething(){
        mapper.scan(classC.class,new DynamoDBScanExpression()).stream();
    }

}  

Test Class

 @RunWith(SpringJUnit4ClassRunner.class)
 class TestClass{

    @InjectMocks
    @Autowired
    ClassA classA;

    @Mock 
    ClassD classD;

    @Autowired
    @Qualifier("dynamodbMapper")
    private DynamoDBMapper mockedDynamoDBMapper;
    // globally mocked in config

        @Test
        public void testWithValidData() {
            A a = new A();
            B b = new B();
            setUp(classA);
            mockDynamoDBCall();
            classA.doSomthing();
        }

           private void setUp(ClassA classA){
               Mockito.when(classD.createValues(a,b)).thenReturn(Matchers.any(Reponse.class)); // problem after mockDynamoDBCall()
           }
           private void mockDynamoDBCall(){
               when(mapper.scan(Mockito.eq(Object.class), Mockito.any(DynamoDBScanExpression.class))).
               thenReturn(mockPaginatedScanList);
               when(mockPaginatedScanList.stream()).thenReturn(createDummyData().stream());
           }
 }

when I was not mocking DynamoDBMapper its working fine.

After mocking DynamoDB mapper it is throwing exception in setUp method

[junit]     Caused an ERROR
[junit]
[junit] Invalid use of argument matchers!
[junit] 2 matchers expected, 1 recorded:
[junit] -> at  <class name>
[junit]
[junit] This exception may occur if matchers are combined with raw values:
[junit]     //incorrect:
[junit]     someMethod(anyObject(), "raw String");
[junit] When using matchers, all arguments have to be provided by matchers.
[junit] For example:
[junit]     //correct:
[junit]     someMethod(anyObject(), eq("String by matcher"));
[junit]
[junit] For more info see javadoc for Matchers class.
[junit]
[junit] org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
[junit] Invalid use of argument matchers!
[junit] 2 matchers expected, 1 recorded:
[junit] -> at <class name>
[junit]
[junit] This exception may occur if matchers are combined with raw values:
[junit]     //incorrect:
[junit]     someMethod(anyObject(), "raw String");
[junit] When using matchers, all arguments have to be provided by matchers.
[junit] For example:
[junit]     //correct:
[junit]     someMethod(anyObject(), eq("String by matcher"));

I tried passing Matchers.any(ClassName) and Matcher.any() as arguments but still i am getting same exception


Answer:

This line

 Mockito.when(classD.createValues(a,b)).thenReturn(Matchers.any(Reponse.class));

makes no sense. You have to tell Mockito what to return. You can't just tell it to return any Response.class. This isn't what matchers do.

Matchers are used for checking the arguments that are passed to a method. They can't be used after thenReturn.

If you fix this up, the error will go away.