通过使用moq模拟另一个方法来测试一个方法是否被调用

本文关键字:方法 一个 调用 是否 moq 模拟 另一个 测试 | 更新日期: 2023-09-27 18:04:56

给定一个类,它有SplitList和Update方法。正在从SplitList调用更新

class BaseClass
{
    public void SplitList(ref IList<Type> objList)
    {
        IList<Type> SplitA = objList.Where(c=>c.currency == "USD").ToList();
        IList<Type> SplitB = objList.Where(c=>c.currency == "GBR").ToList();
        if(SplitA.Count() > 0)
        {
            Update(ref SplitA);
        }
        if(SplitB.Count() > 0)
        {
            Update(ref SplitB);
        }
    }
}

我想测试的是当我调用SplitList方法时,Update被调用了多少次。我的意思是如果两个都被调用,或者一个,或者一个都没有。需要测试该方法的边界条件。

我是如何进行的,

class TestClass
{
    [TestMethod]
    void TestSplitList()
    {
        Mock<BaseClass> mock = new Mock<BaseClass>();
        mock.Setup(m=>m.Update(ref List)).Verifiable();
        mock.Object.SplitList(ref List);
        mock.Verify(m=>m.Update(ref List), Times.Exactly(1));
    }
}

这段代码给我的错误是,期望在模拟上调用恰好1次,但实际上是0次:m => m. update (.List)

有谁能帮帮忙吗?

通过使用moq模拟另一个方法来测试一个方法是否被调用

您的SplitList方法是virtual吗?(因为你发布的代码可能与代码库不相同。)在这种情况下,Moq将覆盖它,你的Update将不会被调用。您可以将其设置为非虚拟的,或者通过添加以下行让Moq调用它:

mock.CallBase = true;

如果选择这种方法,请注意所有方法都将"CallBase"(如果没有期望覆盖成员)。

更新:您在SplitList实现中将不同的列表传递给Update方法。参数是objList,然后创建一个不同的列表(SplitASplitB)并将其中一个传递给Update方法。由于SplitA(或SplitB) != objList,测试失败。

这里真的要用ref吗?如果您删除它,代码将更简单,测试将通过。如果没有ref,列表仍然会有所不同,抱歉我错过了。我认为你可能需要改变你的逻辑,以允许更好的测试…

用虚拟方法模拟类将允许您验证该方法。您也可以使用CallBase同时调用实际的方法(Update)。

假设以下代码(假设您发布的更新不包含ref参数,我已经从Split中删除了不必要的ref):

public class Type
{
    public string currency { get; set; }
    public int OrderNo { get; set; }
}
public class BaseClass
{
    // w.r.t ref, do you mean to reassign objList to the filtered lists?
    public void SplitList(ref IList<Type> objList)
    {
        var SplitA = objList.Where(c => c.currency == "USD").ToList();
        var SplitB = objList.Where(c => c.currency == "GBR").ToList();
        if (SplitA.Count() > 0)
        {
            Update(SplitA);
        }
        if (SplitB.Count() > 0)
        {
            Update(SplitB);
        }
    }
    public virtual IList<Type> Update(IList<Type> updateList)
    {
        int count = 0; 
        foreach (Type objType in updateList)
        {
            objType.OrderNo = count++;
        } 
        return updateList;
    }
}

这个单元测试证明了虚拟Update方法可以被验证,也可以被调用。如果您选择而不是来使用CallBase,则需要调整单元测试,因为列表中元素的突变将不会发生。

[Test]
public void TestSplitList()
{
    var mock = new Mock<BaseClass>();
    mock.CallBase = true; // This will ensure the actual Update also gets called
    IList<Type> fakeTypes = new List<Type>
                        {
                            new Type {currency = "GBR"},
                            new Type {currency = "GBR", OrderNo = 100},
                            new Type {currency = "JPY", OrderNo = 55}
                        };
    mock.Object.SplitList(ref fakeTypes);
    mock.Verify(m => m.Update(It.IsAny<IList<Type>>()), Times.Exactly(1));
    mock.Verify(m => m.Update(It.Is<IList<Type>>(x => x.Any(y => y.currency == "GBR") 
                && x.Count == 2)), Times.Exactly(1));
    mock.Verify(m => m.Update(It.Is<IList<Type>>(
        x => x.Any(y => y.currency == "JPY"))), Times.Never);
    Assert.AreEqual(3, fakeTypes.Count, "List itself must not have changed");
    // These tests show the effect of the actual `Update` method
    Assert.IsTrue(fakeTypes.Any(t => t.OrderNo == 0 && t.currency == "GBR"), 
       "GBR must be ordered");
    Assert.IsTrue(fakeTypes.Any(t => t.OrderNo == 1 && t.currency == "GBR"), 
       "GBR must be ordered");
    Assert.IsFalse(fakeTypes.Any(t => t.OrderNo == 100 && t.currency == "GBR"), 
       "GBR must be ordered");
    Assert.IsTrue(fakeTypes.Any(t => t.OrderNo == 55 && t.currency == "JPY"), 
       "JPY must not be ordered");
}

注意:在当前的代码实现中,对List参数使用ref是不必要的。因为您直接更改了列表中的对象,所以这些更改对于引用这些项的所有集合都是可见的。此外,您没有更改列表本身(只是其中的元素),因此您既不需要使Update参数为ref,也不需要从Update返回它。