繁体   English   中英

数据库函数 Mock 在 c# web api 单元测试中不起作用

[英]Database function Mock is not working in c# web api unit testing

这是我的控制器方法

[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 对象在这里为空。 请帮助,因为我是单元测试用例构建的初学者。

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

在这种情况下,这是对控制器方法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的块,因为无论如何,如果您有未处理的异常,服务器将返回内部服务器错误,因此如果您甚至不记录错误,则捕获它是没有用的。 同样在我throw的第二个 catch 块中,服务器也会自动返回内部服务器错误。 如果您处于调试模式,这可能会很方便,因为您会得到完整的异常返回给您,但是如果您返回InternalServerError() ,即使在调试中您也不会得到任何信息,您必须检查日志...

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

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 件事:

    • 你在测试什么(你的方法)
    • 结果应该是什么(有数据的结果,有错误的结果,引发的异常......等)
    • 在什么条件下会出现这个结果

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

  • 单元测试总是在 3 部分ARRANGEACTASSERT 在每个测试中编写它总是一个好习惯,这样你就可以更好地组织你的代码

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

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

请注意,我在测试方面处于中级水平,因此我向您展示的是我从同事那里学到的良好实践。 但也许有更多经验的开发人员可以提出比我更好的实践。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM