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.