C# 单元测试 Mock 如何获取返回结果

2022年9月26日 1786点热度 0人点赞 0条评论
内容纲要

做单元测试的时候往往会使用 Moq 等库,对代码进行 Mock。
但是有些过程,我们希望除了 Mock 方法之外,能够在 Mock 方法中,判断传递的参数是否正确。

因为常规的 Mock ,是返回一个值。

var mock =  new Mock<Test>();
mock.Setup<xxxx>(x => Get)....

var obj = mock.Object;
var result = obj.Get();

在这个时候,是忽略参数和计算过程,返回一个值。

但是我们如果需要一个参数,根据参数判断,是否验证通过。

笔者的需求是,一个数据库同步任务,根据变化的表或数据,生成 SQL ,然后向目标数据库执行 SQL。
因为是单元测试,所以我需要 Mock 数据库,即 Mock SQLCommand 好的 SQLConnect 两个过程。

我做了个接口,进一步解耦数据库连接:


    public class MysqlDatabaseMock : MysqlDatabase
... 
        protected override async Task ExecteAsync(MySqlCommand dbCommand, string sql)
        {
            _output.WriteLine(sql);
            await Task.CompletedTask;
        }
...

这里是个 protected 方法。

可以看到,返回的是 Task,也就是这个方法没有返回值,因此,我们是不能通过获取返回值,判断这个 SQL 是否生成得跟我们的预估一样。我们要做的,就是在 Mock 方法中,执行我们的代码,并且,需要 让 Assert.Equal(left, right); 生效,也就是,我们这个 Assert.Equal 不是放在 Test 方法中,而是放在 Mock 中。

void xxxTest()
{
    mock.Get()
    ... ...
    Get => Assert.Equal() ....
}

实际写法:

        private static void MockExcete(string sqlStr, Mock<MysqlDatabaseMock> mock)
        {
            Func<MySqlConnection, string, Task> func = (connect, sql) =>
            {
                var left = Format(sqlStr);
                var right = Format(sql);
                Assert.Equal(left, right);
                return Task.CompletedTask;
            };
            mock.Protected().Setup<Task>("ExecteAsync", ItExpr.IsAny<MySqlConnection>(), ItExpr.IsAny<string>())
                .Returns((Delegate)func);

            mock.Protected().Setup<Task>("ExecteAsync", ItExpr.IsAny<MySqlCommand>(), ItExpr.IsAny<string>())
                .Returns((Delegate)func);

            static string Format(string sql)
            {
                var newSql = sql
                    .Replace("\r\n", "")
                    .Replace("\r", "")
                    .Replace("\n", "")
                    .Replace(" ", "");
                if (newSql.EndsWith(';'))
                {
                    return newSql[..^1];
                }
                return newSql;
            }
        }

代码很简单,我们使用一个委托,让 Mock 执行这个方法。

痴者工良

高级程序员劝退师

文章评论