如何验证从模拟列表中删除模拟对象?

时间:2014-03-04 23:11:12

标签: c# list unit-testing mocking moq

我正在尝试为此方法编写单元测试:

public void AddItem( Cart cart, Item item )
{
    var duplicates = cart.Items.OfType<Item>()
                         .Where( i => i.Key == item.Key )
                         .ToList();

    foreach ( var duplicate in duplicates )
    {
        cart.Items.Remove( duplicate );
    }

    cart.Items.Add( item );
}

我想验证在添加新项目之前,是否从列表中删除了重复项目(具有相同键的项目)。此测试失败:

[TestMethod]
public void AddItemRemovesDuplicateItemsFirst()
{
    const int itemKey = 123;

    var cart = new Mock<Cart>();

    var duplicate = new Mock<Item>();
    duplicate.SetupGet( m => m.Key ).Returns( itemKey );

    cart.SetupGet( m => m.Items ).Returns( new List<Item>
        {
            duplicate.Object
        } );

    var addItem = new Mock<Item>();
    addItem.SetupGet( m => m.Key ).Returns( itemKey );

    var task = GetTask();
    task.AddItem( cart.Object, addItem.Object );

    Assert.AreEqual( 1, cart.Object.Items.Count );
}

失败,因为Count是2.另一方面,此测试通过:

[TestMethod]
public void AddItemRemovesDuplicateItemsFirst()
{
    const int itemKey = 123;

    var cart = new Mock<Cart>();

    cart.SetupGet( m => m.Items ).Returns( new List<Item>
        {
            new Item
                {
                    Key = itemKey
                }
        } );

    var addItem = new Mock<Item>();
    addItem.SetupGet( m => m.Key ).Returns( itemKey );

    var task = GetTask();
    task.AddItem( cart.Object, addItem.Object );

    Assert.AreEqual( 1, cart.Object.Items.Count );
}

唯一的区别是第二个不使用Mock对象作为列表项。

我试图避免像第二种方法那样实际创建对象。我怎样才能让第一个通过?

2 个答案:

答案 0 :(得分:1)

由于您正在测试与Items的交互,因此您将需要一个对象来测试,无论是具体实例(如第二种方法)还是模拟。

如果您使用模拟,则需要将其设置为在调用OfType时返回现有项目。然后,您可以明确验证是否已调用Remove,而不是检查Count,类似这样(未经测试):

Items.Verify(
    x => x.Remove(
        It.Is<Item>(
            item => item.Key == itemKey)));

话虽如此,我不禁想到这个功能应该封装在Cart中,而不会暴露Item列表以便在该类之外进行操作。这样也可以使编写测试变得更容易。

答案 1 :(得分:1)

使用模拟进行测试时(例如Mock<Cart>),您正在测试受测试的代码如何与模拟进行交互。使用真实对象(例如Cart)进行测试时,您正在测试最终结果

因此,在测试失败的情况下,因为您正在使用模拟,所以您需要测试模拟与之交互的方式。因此,您希望测试代码是否正确调用Remove方法:

    cart.Verify(x => x.Remove(It.IsAny<Item>()));

这不是很好,因为您要将It.IsAny<Item>()替换为实际预期的项目。这只意味着您也将为项目列表设置模拟:

    var itemsListMock = new Mock<List<Item>>();
    cart.SetupGet( m => m.Items ).Returns(itemsListMock.Object);
    //TODO setup getting 'duplicates' from Where clause

希望这有点清楚并且有意义。