Faked out DynamoDBMapper

Wanna try DynamoDB without using your network interface ?
Not requiring connection will allow any developer on any platform to run your tests without setup pain or docker installation.

Below is a mapper able to do the two most basic operations necessary to read and write data : save and query.
You will be able to fake out the database and to concentrate on the business logic in your tests.
Who wants to use mocked behaviors with dynamo when you can just use things like … HashMaps ?


import com.amazonaws.services.dynamodbv2.datamodeling.*;
import com.amazonaws.services.dynamodbv2.model.QueryRequest;
import com.amazonaws.services.dynamodbv2.model.QueryResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

@Configuration
class MockDynamoConfiguration {

    @Primary
    @Bean
    public DynamoDBMapper mockedDynamoDBMapper() {
        return new DynamoDBMapper(null) {
            private Map, Set> tables = new HashMap();

            @Override
            public <T> void save(T object) {
                tables.computeIfAbsent(object.getClass().getName(), k -> new HashSet()).add(object);
            }

            @Override
            public <T> PaginatedQueryList<T> query(Class<T> clazz, DynamoDBQueryExpression<T> queryExpression) {
                T candidate = queryExpression.getHashKeyValues();
                Object hashKey = findHashKey(candidate, queryExpression);
                T foundElement = findElement(clazz, hashKey, queryExpression.getIndexName());
                List<T> results = foundElement == null ? emptyList() :
                        singletonList(findElement(clazz, hashKey, queryExpression.getIndexName()));
                return new PaginatedQueryList<T>(
                        this, clazz, null, new QueryRequest(), new QueryResult().withItems(emptyList()),
                        DynamoDBMapperConfig.PaginationLoadingStrategy.EAGER_LOADING, null) {
                    @Override
                    public T get(int n) {
                        return results.get(n);
                    }

                    @Override
                    public boolean isEmpty() {
                        return results.isEmpty();
                    }

                    @Override
                    public Iterator<T> iterator() {
                        return results.iterator();
                    }
                };
            }

            private <T> List findFields(Class<T> clazz, String indexName) {
                return stream(clazz.getDeclaredFields()).filter(f ->
                        (indexName == null && f.getAnnotationsByType(DynamoDBHashKey.class).length > 0
                                || indexName != null &&
                                ((f.getAnnotationsByType(DynamoDBAttribute.class).length > 0 &&
                                        Objects.equals(f.getAnnotationsByType(DynamoDBAttribute.class)[0].attributeName(), indexName)) ||
                                        (f.getAnnotationsByType(DynamoDBIndexHashKey.class).length > 0 &&
                                                Objects.equals(f.getAnnotationsByType(DynamoDBIndexHashKey.class)[0].globalSecondaryIndexName(), indexName)))))
                        .collect(Collectors.toList());
            }

            private <T> Object findHashKey(T candidate, DynamoDBQueryExpression<T> queryExpression) {
                return findFields(candidate.getClass(), queryExpression == null ? null : queryExpression.getIndexName()).stream()
                        .findFirst().map(f -> {
                            try {
                                f.setAccessible(true);
                                return f.get(candidate);
                            } catch (IllegalAccessException e) {
                                return null;
                            }
                        }).orElse(null);
            }

            @Override
            public <T> T load(Class<T> clazz, Object hashKey) {
                return findElement(clazz, hashKey, null);
            }

            @SuppressWarnings("unchecked")
            public <T> T findElement(Class<T> clazz, Object hashKey, String indexName) {
                Set<T> set = (Set<T>) tables.computeIfAbsent(clazz.getName(), k -> new HashSet());
                List keyFields = findFields(clazz, indexName);

                return set.stream().filter(element -> keyFields.stream().anyMatch(f -> {
                    try {
                        f.setAccessible(true);
                        return Objects.equals(f.get(element), hashKey);
                    } catch (IllegalAccessException e) {
                        return false;
                    }
                })).findFirst().orElse(null);
            }
        };
    }
}

Now make it work… how ? just keep it simple.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.