【问题标题】:Database function Mock is not working in c# web api unit testing数据库函数 Mock 在 c# web api 单元测试中不起作用
【发布时间】:2020-09-25 07:28:43
【问题描述】:

这是我的控制器方法

[HttpPost]
    [Authorize]
    [Route(RouteConfig.Routes.LovList.contactStatus)]
    public IHttpActionResult ContactStatusList()
    {
        try
        {
            var result = new DBClass.HeroDb().GetList(
                            DBClass.DBConstants.ListConstants.query_Contact_Status);
            return Json(new Models.Response(
                        Models.ResponseMessages.Success,
                        result)
                     );
        }
        catch(System.Data.SqlClient.SqlException)
        {
            return InternalServerError();
        }
        catch(System.Exception ex)
        {
            Logger.Error(ex, ex.Message, ex.StackTrace);
            return InternalServerError();
        }
        
    }

这是我的测试用例方法

[TestMethod()]
    public void ContactStatusListTest()
    {
        
        Mock<DBClass.HeroDb> mock = new Mock<DBClass.HeroDb>();
        mock.Setup(x => x.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status))
            .Returns(CreateContactList());
        var result = new ListController().ContactStatusList();
        Models.Response response = (Models.Response)result;
        Assert.AreEqual(response.Message, Models.ResponseMessages.Success);
        Assert.IsNotNull(response.Data);
    }
    public System.Data.DataTable CreateContactList()
    {
        DataTable table = new DataTable();
        table.Columns.Add("ContactStatus");
        DataRow row1 = table.NewRow();row1["ContactStatus"] = "Contacted"; table.Rows.Add(row1);
        DataRow row2 = table.NewRow(); row2["ContactStatus"] = "Not Contacted"; table.Rows.Add(row2);
        DataRow row3 = table.NewRow(); row3["ContactStatus"] = "Contacted"; table.Rows.Add(row3);
        return table;
    }

我试图在我的测试方法中模拟 GetList() 函数,但它不起作用。控制器方法给出内部服务器错误。因为控制要

var result = new DBClass.HeroDb()
            .GetList(DBClass.DBConstants.ListConstants.query_Contact_Status);

此行和 db 对象在此处为空。请帮助,因为我是单元测试用例构建的初学者。

【问题讨论】:

    标签: c# unit-testing mocking webapi rest


    【解决方案1】:

    首先让我们先打好基础:单元测试不同于集成测试

    在这种情况下,这是对控制器方法的单元测试 ContactStatusList。您仅测试此方法,并且您实际上通过尝试模拟您的 HeroDb 对象来正确地做事。请注意,您决定模拟此对象是因为 这是一个依赖项

    问题是您设置了 Mock 但您没有使用它,因为在您的 ContactStatusList 方法中您调用了 new DBClass.HeroDb()

    第二个问题是你试图模拟一个类。这实际上是可能的,但是您要模拟的所有类的方法都必须声明为虚拟的。因此,实际上最好模拟一个接口。

    这个接口应该在你的ListController的构造函数中接收。在您的 Web 项目的常规执行中,在启动时注入该接口的一个实例,但在单元测试中将您的模拟提供给 ListController 的构造函数。

    记住这条规则:任何依赖都应该被控制器的构造函数接收

    这是你的界面和你的 DbHero 类

    public interface IDbHero
    {
        IEnumerable<Contact> GetList(QueryContactStatus status);
    }
    
    public class DbHero : IDbHero
    {
        public IEnumerable<Contact> GetList(QueryContactStatus status)
        {
            // Implementation here
        }
    }
    

    现在这是你的控制器:

    [ApiController]
    [Route("api/[controller]")]
    public class ListController: ControllerBase
    {
        private readonly IHeroDb _heroDb;
    
        public ListController(IHeroDb heroDb)
        {
            _heroDb = heroDb ?? throw new ArgumentNullException(nameof(heroDb));
        }
        
        [HttpPost]
        [Authorize]
        [Route(RouteConfig.Routes.LovList.contactStatus)]
        public IHttpActionResult ContactStatusList()
        {
            try
            {
                var result = _heroDb.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status);
                return Json(new Models.Response(
                            Models.ResponseMessages.Success,
                            result)
                         );
            }
            catch(System.Exception ex)
            {
                Logger.Error(ex, ex.Message, ex.StackTrace);
                throw;
            }
        }
    }
    

    请注意,我删除了仅捕获 SqlException 的块,因为无论如何,如果您有未处理的异常,服务器将返回内部服务器错误,因此即使您不记录错误也无法捕获它。 同样在第二个 catch 块中我只是 throw 所以服务器也会自动返回一个内部服务器错误。如果您处于调试模式,这可能会很方便,因为您会收到完整的异常返回给您,但如果您返回 InternalServerError(),即使在调试中您也不会得到任何信息,您必须检查日志......

    Startup.cs 类的ConfigureServices 方法中,注入IDbHero 接口的实现。请注意,这是一个作用域服务,这意味着将为每个 HTTP 请求创建一个新实例。就我个人而言,我从不将我的数据库访问层作为 singleton 注入,因为这可能会导致一些问题,具体取决于该层的实现方式。例如 EF Core 的 DbContextsingleton 模式不兼容。

    services.AddScoped<IDbHero>(_ => new DBClass.HeroDb(Configuration.GetConnectionString("DbHeroConnectionString")));
    

    我不知道你如何处理与数据库的连接,因为在你的代码示例中没有提到连接字符串,但我会做类似上面的事情。

    您的连接字符串来自您的 appsettings.json 配置文件

    "ConnectionStrings": {
        "DbHeroConnectionString": "YourConnectionString"
      }
    

    现在要在单元测试中使用您的模拟对象,只需这样做:

    [TestMethod()]
    public void ContactStatusList_ShouldReturnData_WhenCalled()
    {
        // ARRANGE
        var mock = new Mock<IHeroDb>();
        mock.Setup(x => x.GetList(DBClass.DBConstants.ListConstants.query_Contact_Status))
            .Returns(CreateContactList());
        
        var sut = new ListController(mock.Object);
            
        // ACT
        var result = sut.ContactStatusList();
        
        // ASSERT
        Models.Response response = (Models.Response)result;
        Assert.AreEqual(response.Message, Models.ResponseMessages.Success);
        Assert.IsNotNull(response.Data);
    }
    

    请注意这里的一些事情:

    • 你的单元测试名称:这应该显示 3 件事:

      • 您正在测试什么(您的方法)
      • 结果应该是什么(有数据的结果、有错误的结果、引发的异常……等等)
      • 在什么条件下会出现这种结果

      您可以例如测试当参数具有不正确的值时方法是否返回错误。 这应该在另一个单元测试中测试

    • 单元测试总是分为ARRANGEACTASSERT 三个部分。在每个测试中编写它始终是一个好习惯,这样您就可以更好地组织代码

    • sut 表示System Under Test:这是您要测试的内容,所有其他依赖项(例如您的DbHero 层)都应该被模拟。

    现在下一步是编写一个单元测试来测试您的DbHero.GetList。 这次您将创建DbHero 类的真实实例(不是模拟),因为这是您要测试的内容:这是您的sut。 p>

    请注意,我的测试水平处于中等水平,因此我向您展示的是我从同事那里学到的良好做法。但也许一些经验丰富的开发人员可以提出比我更好的做法。

    【讨论】:

    • 感谢您的明确解释@Jerome。我又进了一步。现在在ListController (我在哪里返回 Json) 给出错误。我应该也必须模拟它
    • 不,你应该模拟你的ListController,因为这是你想要测试的,测试是模拟是没有用的,因为你想要测试的是你的真实的 实施。仅模拟其依赖项(如IDbHero)。在catch 块中设置断点并分析异常。这是 Json 序列化的错误,可能是循环引用或类似的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 2020-04-08
    • 2021-06-10
    相关资源
    最近更新 更多