我有以下简单的DynamoDBDao,其中包含一个查询索引并返回结果映射的方法。
import com.amazonaws.services.dynamodbv2.document.*;
public class DynamoDBDao implements Dao{
private Table table;
private Index regionIndex;
public DynamoDBDao(Table table) {
this.table = table;
}
@PostConstruct
void initialize(){
this.regionIndex = table.getIndex(GSI_REGION_INDEX);
}
@Override
public Map<String, Long> read(String region) {
ItemCollection<QueryOutcome> items = regionIndex.query(ATTR_REGION, region);
Map<String, Long> results = new HashMap<>();
for (Item item : items) {
String key = item.getString(PRIMARY_KEY);
long value = item.getLong(ATTR_VALUE);
results.put(key, value);
}
return results;
}
}
我正在尝试编写一个单元测试,用于验证当DynamoDB索引返回ItemCollection时,Dao会返回相应的结果映射。
public class DynamoDBDaoTest {
private String key1 = "key1";
private String key2 = "key2";
private String key3 = "key3";
private Long value1 = 1l;
private Long value2 = 2l;
private Long value3 = 3l;
@InjectMocks
private DynamoDBDao dynamoDBDao;
@Mock
private Table table;
@Mock
private Index regionIndex;
@Mock
ItemCollection<QueryOutcome> items;
@Mock
Iterator iterator;
@Mock
private Item item1;
@Mock
private Item item2;
@Mock
private Item item3;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(table.getIndex(DaoDynamo.GSI_REGION_INDEX)).thenReturn(regionIndex);
dynamoDBDao.initialize();
when(item1.getString(anyString())).thenReturn(key1);
when(item1.getLong(anyString())).thenReturn(value1);
when(item2.getString(anyString())).thenReturn(key2);
when(item2.getLong(anyString())).thenReturn(value2);
when(item3.getString(anyString())).thenReturn(key3);
when(item3.getLong(anyString())).thenReturn(value3);
}
@Test
public void shouldReturnResultsMapWhenQueryReturnsItemCollection(){
when(regionIndex.query(anyString(), anyString())).thenReturn(items);
when(items.iterator()).thenReturn(iterator);
when(iterator.hasNext())
.thenReturn(true)
.thenReturn(true)
.thenReturn(true)
.thenReturn(false);
when(iterator.next())
.thenReturn(item1)
.thenReturn(item2)
.thenReturn(item3);
Map<String, Long> results = soaDynamoDbDao.readAll("region");
assertThat(results.size(), is(3));
assertThat(results.get(key1), is(value1));
assertThat(results.get(key2), is(value2));
assertThat(results.get(key3), is(value3));
}
}
我的问题是items.iterator()实际上没有返回Iterator它返回一个IteratorSupport,它是DynamoDB文档API中的包私有类。这意味着我实际上无法像上面那样嘲笑它,所以我无法完成剩下的测试。
在这种情况下我该怎么办?如果在DynamoDB文档API中使用这个糟糕的包私有类,我如何正确地单元测试我的DAO?
答案 0 :(得分:6)
首先,单元测试永远不应该尝试验证对象内部的私有状态。它可以改变。 如果该类没有通过非私有getter方法公开其状态,那么它的测试业务就不是它的实现方式。
其次,你为什么关心迭代器的实现? 该类通过返回迭代器(接口)来履行其合同 迭代时将返回它应该的对象。
第三,你为什么要嘲笑你不需要的东西?
构建模拟对象的输入和输出,不要嘲笑它们;这是不必要的。
您将表传递给构造函数?精细。
然后扩展Table类,为您需要的任何保护方法。
将受保护的getter和/或setter添加到Table子类中。
如有必要,让他们返回硬编码值。它们并不重要。
请记住,只测试测试类中的一个类。 您正在测试dao而不是表,也不测量索引。
答案 1 :(得分:1)
Dynamodb api有很多这样的类,不容易被嘲笑。这导致花费大量时间编写复杂的测试和更改功能是很大的痛苦。
我认为,对于这种情况,更好的方法是不要尝试采用传统方式并使用AWS团队使用DynamodbLocal库 - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html
这基本上是DyanamoDB的内存实现。我们编写了测试,以便在单元测试初始化期间,将生成DyanmodbLocal实例并创建表。这使得测试变得轻而易举。我们尚未在库中发现任何错误,并且由AWS积极支持和开发。强烈推荐它。
答案 2 :(得分:0)
一种可能的解决方法是定义一个测试类,该测试类将IteratorSupport
扩展到它所在的同一个包中,并定义所需的行为
然后,您可以通过测试用例中的模拟设置返回此类的实例。
当然,这不是一个好的解决方案,而只是一个解决方法,原因与@Jeff Bowman在评论中提到的相同。
答案 3 :(得分:0)
将ItemCollection检索提取到单独的方法可能会更好吗? 在您的情况下,它可能如下所示:
public class DynamoDBDao {
protected Iterable<Item> readItems(String region) { // can be overridden/mocked in unit tests
// ItemCollection implements Iterable, since ItemCollection-specific methods are not used in the DAO we can return it as Iterable instance
return regionIndex.query(ATTR_REGION, region);
}
}
然后在单元测试中:
private List<Item> mockItems = new ArrayList<>(); // so you can set these items in your test method
private DynamoDBDao dao = new DynamoDBDao(table) {
@Override
protected Iterable<Item> readItems(String region) {
return mockItems;
}
}
答案 4 :(得分:0)
当您使用when(items.iterator()).thenReturn(iterator);
时,Mockito会将项目视为ItemCollection,从而导致编译错误。在您的测试用例中,您希望将ItemCollection视为Iterable。因此,简单的解决方案是将项目转换为Iterable,如下所示:
when(((Iterable<QueryOutcome>)items).iterator()).thenReturn(iterator);
还要将迭代器设为
@Mock
Iterator<QueryOutcome> iterator;
这应该在没有警告的情况下修复代码:)
如果这样可以解决问题,请接受答案。
答案 5 :(得分:0)
您可以使用以下虚假对象测试您的读取方法:
public class DynamoDBDaoTest {
@Mock
private Table table;
@Mock
private Index regionIndex;
@InjectMocks
private DynamoDBDao dynamoDBDao;
public DynamoDBDaoTest() {
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(table.getIndex(GSI_REGION_INDEX)).thenReturn(regionIndex);
dynamoDBDao.initialize();
}
@Test
public void shouldReturnResultsMapWhenQueryReturnsItemCollection() {
when(regionIndex.query(anyString(), anyString())).thenReturn(new FakeItemCollection());
final Map<String, Long> results = dynamoDBDao.read("region");
assertThat(results, allOf(hasEntry("key1", 1l), hasEntry("key2", 2l), hasEntry("key3", 3l)));
}
private static class FakeItemCollection extends ItemCollection<QueryOutcome> {
@Override
public Page<Item, QueryOutcome> firstPage() {
return new FakePage();
}
@Override
public Integer getMaxResultSize() {
return null;
}
}
private static class FakePage extends Page<Item, QueryOutcome> {
private final static List<Item> items = new ArrayList<Item>();
public FakePage() {
super(items, new QueryOutcome(new QueryResult()));
final Item item1= new Item();
item1.with(PRIMARY_KEY, "key1");
item1.withLong(ATTR_VALUE, 1l);
items.add(item1);
final Item item2 = new Item();
item2.with(PRIMARY_KEY, "key2");
item2.withLong(ATTR_VALUE, 2l);
items.add(item2);
final Item item3 = new Item();
item3.with(PRIMARY_KEY, "key3");
item3.withLong(ATTR_VALUE, 3l);
items.add(item3);
}
@Override
public boolean hasNextPage() {
return false;
}
@Override
public Page<Item, QueryOutcome> nextPage() {
return null;
}
}