首页 > 解决方案 > 在没有实际数据库调用的情况下在具有 N 层的 Web api 中进行单元测试

问题描述

我正在为我的 Web API 应用程序编写单元测试,使用 .net Core 创建。我使用 MSTest 和 MOQ 进行模拟。

它有很多层,如下所示。

Controller => Manager 类 => Facade 类 => Contract 类

我在我模拟的控制器中提供实体。

[TestMethod]
public void InvoiceUpdateFinanceTest_Success()
{
    var mock = mockFactory.OutstandingInvoicesDetailsMock();

    dataController.InvoiceUpdateFinance(mock);
    Assert.IsTrue(true);
}

合同类正在创建数据库连接实例并进行数据库调用

合约层代码示例如下

public class GradeWiseSlab : IMaintainContract
{
  

    IDbConnection _db = new DBModel().InstanceCreation();
    
    DynamicParameters dp = new DynamicParameters();
    public MaintainContractEntities MaintainContractEntities { get; set; }
    public void SaveData()
    {
        dp.Add("@N_VENDOR_ID", MaintainContractEntities.VendorId);
        dp.Add("@N_CONTRACT_SUB_TYPE_ID", MaintainContractEntities.SubContractType);
        dp.Add("@N_PAYMENT_TERM", MaintainContractEntities.PaymentTerm);
        dp.Add("@S_TRANSACTION_CURRENCY", MaintainContractEntities.TransactionCurrency);
        dp.Add("@S_DOC_NAME", MaintainContractEntities.DocNames);
        dp.Add("@S_APPROVAL_STATUS", MaintainContractEntities.ApproalStatus);
        dp.Add("@D_FROM_DATE", MaintainContractEntities.FromDate);
        dp.Add("@D_TO_DATE", MaintainContractEntities.ToDate);
        this._db.Query(Constant.SP_GRADE_WISE_SLAB, dp, commandTimeout: 0, commandType: CommandType.StoredProcedure);
}

我如何编写覆盖所有层的测试用例,即从控制器到合同,但没有实际的数据库要触摸?

我没有使用实体框架,我使用的是 Dapper 库

标签: unit-testingasp.net-web-apimstestweb-api-testingvs-unit-testing-framework

解决方案


如果您使用的是 EntityFramework,EF 现在有一个 InMemoryDatabase。

但情况似乎并非如此。

我必须重写你的一些“术语”,因为你没有显示很多代码并且术语有点混乱。

Controller (WebApiLayer)=> Manager classes (BusinessLogic Layer) => DataLayer(这是对存储过程的调用实际存在的地方)。根据您的描述,我会说我的“DataLayer”是您的“Contract”层......恕我直言,“Contract”是一个令人困惑和模棱两可的名称......对于数据层来说不是一个好名字。但我离题了。

通过上述设计,您可以将一些 IDataLayer 对象(例如,IEmployeeDataLayer)接口注入到您的经理(EmployeeManager:IEmployeeManager)中。您将拥有一个“真实的”具体的 EmployeeTheRealOneDataLayer 对象……并且您还可以模拟 IEmployeeDomainDataLayer .. 以便对您的 EmployeeManager 对象进行单元测试。

我实际上在这里有一个“关闭”示例:

https://github.com/granadacoder/dotnet-core-on-linux-one/blob/master/src/Bal/Managers/EmployeeManager.cs

public class EmployeeManager : IEmployeeManager
{
    private readonly ILogger<EmployeeManager> logger;
    private readonly IEmployeeDataLayer iedl;

    public EmployeeManager(ILoggerFactory loggerFactory, IEmployeeDataLayer iedl)
    {
        this.logger = loggerFactory.CreateLogger<EmployeeManager>();
        this.iedl = iedl;
    }

使用我的单元测试代码,我将模拟 IEmployeeDataLayer ,以便能够测试 EmployeeManager 类。

您的问题是..我如何在不访问真实数据库的情况下测试“真实” EmployeeDataLayer。

这是我不认为它存在的突破点。数据层中的所有代码都被编码为(真的)访问数据库。

所以这就是圣战开始的地方。有人会说“将它连接到 sqlexpress 数据库”......但是恕我直言,当这些停止成为单元测试(不仅仅在内存中运行)......而是成为更多功能测试时。

由于您没有使用 EF,因此您可能必须做出决定。对您的业务逻辑层编写单元测试“没问题”......并“跳过”DataLayer。事实上,我会将以下属性放在我的数据层类中。

https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute?view=netcore-3.1

您的另一个选择是转向更像这样的东西:(内存中的EF)

https://docs.microsoft.com/en-us/ef/core/providers/in-memory/?tabs=dotnet-core-cli

就个人而言,(实际上我在不使用 EF 时会这样做)(而且我也使用 Dapper,因为 Dapper 性能良好)......我跳过了 UNIT 测试数据层。(强调“单元”测试)

但是我们的 QA 会针对真实的数据库引擎(想想 SqlSExpress)编写 FUNCTIONAL 测试。这里需要注意的是,他们必须能够(我们每周选择一次)完全删除/刷新“qa 数据库”......并从种子数据中重建它。这可以防止人们编写功能测试......在表格上寻找硬编码的代理(主键)......但强制通过唯一约束来寻找项目。又名,它可以防止与 qa 功能测试数据库中的特定数据“过于结合”)。(注意,这与 EF In Memory Database 的概念类似……因为当您使用 EF In Memory db 启动单元测试时,它在启动时基本上是“空的”)。

附言

您可以在此处查看更多面向 EF 的项目设置: https ://github.com/granadacoder/oracle-ef-issues-demo/tree/master/src

聚苯乙烯

我并不是说上面的想法是一个详尽的可能性列表。但是当其他想法开始越过“只是在记忆中”的界限时......这就是这些迷你圣战开始的时候。我对我们公司的“记忆中”线非常坚定。我们曾经有很多“MyProject.Tests.csproj”,这可以指单元测试或功能测试。现在我“强烈鼓励”将项目命名为“MyProject.UnitTests.csproj”或“MyProject.FunctionalTests.csproj”以消除歧义。不是每个人都喜欢我的“在沙滩上画线”的方法。


推荐阅读