Mockito:模拟包私人课程



我有以下简单的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(( 实际上并没有返回迭代器,它返回了一个 IteratorSupport,它是 DynamoDB 文档 API 中的一个包私有类。这意味着我实际上不能像上面那样模拟它,所以我无法完成其余的测试。

在这种情况下我该怎么办?在 DynamoDB 文档 API 中给定这个糟糕的包私有类的情况下,如何正确对我的 DAO 进行单元测试?

首先,单元测试永远不应该尝试验证对象内部的私有状态。 它可以改变。如果该类没有通过非私有 getter 方法公开其状态,那么如何实现它与测试无关。

其次,你为什么关心迭代器有什么实现?该类通过返回迭代器(接口(来履行其协定当迭代时,它将返回它应该返回的对象。

第三,你为什么要嘲笑你不需要的对象?为模拟对象构建输入和输出,不要模拟它们;这是不必要的。您将表传递到构造函数中? 好。
然后扩展 Table 类,以便根据需要创建任何受保护的方法。将受保护的 getter 和/或 setter 添加到您的 Table 子类中。如有必要,让它们返回硬编码值。 他们无所谓。

请记住,在测试类中只测试一个类。您正在测试 dao,而不是表或索引。

Dynamodb api 有很多这样的类,它们不容易被嘲笑。这导致花费大量时间在编写复杂的测试上,并且更改功能是很大的痛苦。

我认为,对于这种情况,更好的方法是不要尝试采用传统方式并使用 AWS 团队的 DynamodbLocal 库 - http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html

这基本上是DyanamoDB的内存实现。我们已经编写了测试,以便在单元测试初始化期间,将生成 DyanmodbLocal 实例并创建表。这使得测试变得轻而易举。我们尚未在库中发现任何错误,并且它得到了 AWS 的积极支持和开发。强烈推荐它。

一种可能的解决方法是定义一个测试类,该测试类在它存在的同一包中扩展IteratorSupport,并定义所需的行为

然后,您可以通过测试用例中的模拟设置返回此类的实例。

当然,这不是一个好的解决方案,而只是一种解决方法,原因与@Jeff Bowman在评论中提到的相同。

也许将

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;
  }
}

当您使用when(items.iterator()).thenReturn(iterator); Mockito时,会将项目视为ItemCollection,从而导致编译错误。在您的测试用例中,您希望将 ItemCollection 视为一个可迭代对象。因此,简单的解决方案是将项目转换为可迭代,如下所示:

when(((Iterable<QueryOutcome>)items).iterator()).thenReturn(iterator);

同时将迭代器设为

@Mock
Iterator<QueryOutcome> iterator;

这应该在没有警告的情况下修复代码:)

如果这解决了问题,请接受答案。

您可以使用如下的假对象来测试读取方法:

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;
    }
}
ItemCollection<QueryOutcome> items = new ItemCollection<QueryOutcome>() {
        @Override
        public Integer getMaxResultSize() {
            return 0;
        }
        @Override
        public Page<Item, QueryOutcome> firstPage() {
            return null;
        }
    };
    Mockito.when(index.query(Mockito.any(QuerySpec.class))).thenReturn(items);
    QueryResult queryResult = new QueryResult();
    Mockito.when(dynamoDBClient.query(Mockito.any(QueryRequest.class))).thenReturn(queryResult);

相关内容

  • 没有找到相关文章

最新更新