我正在尝试为此方法编写单元测试:
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
对象作为列表项。
我试图避免像第二种方法那样实际创建对象。我怎样才能让第一个通过?
答案 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
希望这有点清楚并且有意义。