使用Moq在C#中测试回调函数

3
学习 Moq,需要在测试回调结果方面寻求帮助。结构如下:我有一个 DateManager 对象,它依赖于一个 DateServer 对象,该对象对服务器进行异步调用。 DateServer.GetValidDate 将接收一个 yyyy-MM-dd 格式的日期字符串,并使用相同格式的字符串执行给定的回调函数。
如何设置模拟服务器,使得下面的测试通过?简单的模拟设置是,我的模拟 DateServer 只需返回提供给它的任何字符串(在回调中)。
这是我的设置。我认为我已经正确地设置了大部分结构,但需要帮助填写下面代码中的 ????。如果我应该更好地测试回调返回值,那么我也很感兴趣。
public interface IDateServer
{
    // with a currencyPair (ie: USDCAD), verifies that the given date is 
    // valid for a potential transaction.  
    // Callback is used to process the returned result, which is a date string.
    void GetValidDate(string currencyPair, string date, Action<string> callback);
}

public interface IDateManager
{
    void GetDate(string currencyPair, string dateCode, Action<string> callback);
}

[TestClass]
public class DateManagerTests
{
    [TestMethod]
    public void GetDateTest()
    {
        ManualResetEvent ar = new ManualResetEvent(false);

        Mock<IDateServer> server = new Mock<IDateServer>();
        DateTime tradeDate = new DateTime(2016, 2, 17);

        server.Setup( ???? );

        IDateManager dm = new DateManager(tradeDate, server);

        string ret = "";
        dm.GetDate("USDCAD", "2016-02-17", (string s) =>
        {
            ret = s;
            ar.Set();
        });

        ar.WaitOne();

        Assert.AreEqual<string>(ret, "2016-02-17");

    }

}
2个回答

2

这将为GetValidDate设置回调函数,并在模拟接口上调用该方法时,使您可以访问传递给该方法的任何参数。

Mock<IDateServer> server = new Mock<IDateServer>();

const string testDateString = "2016-02-17";
const string testCurrencyPair = "USDCAD";

server.Setup(obj => obj.GetValidDate(It.IsAny<string>(), 
                                     It.IsAny<string>(), 
                                     It.IsAny<Action<string>>()))
      .Callback<string, string, Action<string>>((currencyPair, date, callback) =>
      {
            //  The parameters passed into GetValidDate (see below) 
            //  will be available in here.
            Debug.WriteLine(currencyPair);
            Debug.WriteLine(date);
      });

server.Object.GetValidDate(testCurrencyPair, testDateString, null);

据我理解,Callback方法只会通知您模拟的方法是否被调用以及使用了哪些参数。

我不确定这对我来说是否清楚。我想要断言testDateString与回调中返回的内容匹配。 - gdbj
在回调函数中,将 Debug 调用替换为 Assert.AreEqual(testDateString, date) - MotoSV
回调函数中指定的参数,即 currencyPairdatecallback,将包含任何对 server.Object.GetValidDate 的调用中传递的值,即使是从 DateManager 中调用。 - MotoSV
只是顺便提一下,我假设 DateManager 构造函数的第二个参数是 IDateServer。如果是这样的话,那么当你执行 new DateManager(....) 时,我会传递 server.Object,它将代表您接口的实际模拟实例。 - MotoSV
希望我也能将您的答案标记为正确,因为回调功能对我非常有帮助。 - gdbj
很高兴它能在某种程度上帮到你。 - MotoSV

2
如果您想测试 DateManager,您需要确保 DateManager 使用特定的参数调用 DateServer 依赖的 GetValidDate 方法。该方法不返回任何内容,因此无需设置。
由于调用回调方法的 DataServer 不是被测试的类,因此回调是否真正被调用并不重要。您需要在 DateServer 的单独测试中进行测试。
因此,以下测试方法应验证 DateManager 是否将正确的参数传递给 DateServer(在这种情况下,“正确”指已传递给 DateManager 的参数):
[TestMethod]
public void GetDateTest()
{
    Mock<IDateServer> server = new Mock<IDateServer>();
    Action<string> callback = (string s) => { };
    IDateManager dm = new DateManager(server.Object);
    dm.GetDate("USDCAD", "2016-02-17", callback);
    server.Verify(x => x.GetValidDate("USDCAD", "2016-02-17", callback));
}

我们调用了 dm.GetDate(),然后 Verify() 检查 GetValidDate() 是否被正确调用。这很有道理,对于这种情况大部分已经解决了。但是,如果我的 DateManager 在最后回调之前操作返回的结果会发生什么?我仍然想检查在回调中返回的字符串。该如何做到这一点? - gdbj
据我所知,DateServer 应该调用回调函数。因此,从 DateManager 的测试角度来看,无需检查回调是否真正被调用。在 DateServer 的单独测试中,您将像在示例代码中一样为 DateServer 提供一个虚拟回调(不使用 Mock)。此外,如果预计 DateManager 在某个时候会调用回调,则需要提供虚拟回调并检查是否已正确调用了该回调的次数。由于调用不能来自模拟的 DateServer,因此是 DateManager 调用了它。 - Markus
是的,我现在明白你是正确的。任何断言检查都不需要,因为结果是由设置本身构造出来的。我花了一点时间才弄清楚这一点。 - gdbj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接