【问题标题】:How to unit test a function in .NET Core 3.1 that uses ExecuteSqlRawAsync to call a stored procedure?如何对 .NET Core 3.1 中使用 ExecuteSqlRawAsync 调用存储过程的函数进行单元测试?
【发布时间】:2020-04-01 07:59:57
【问题描述】:

我正在 ASP.NET Core 3.1 中编写 API,使用 EF Core 访问 SQL Server 数据库。我在 API 中有一个函数,它需要调用一个带有多个输入参数和一个输出参数的存储过程。该函数的简化版本如下。

我正在使用DbContext 和.UseInMemoryDatabase() 进行其他测试,但内存数据库不能与存储过程一起使用。

(此解决方案是数据库优先,而不是代码优先。如果需要,可以更改存储过程,但如果我不必这样做会更好。我可以更改我的 C# 函数来调用存储过程如果有帮助的话,另一种方式。)

如何对这个函数进行单元测试?

public class MyFoo : IFoo
{
    public ApplicationDbContext DbContext { get; }

    public MyFoo(ApplicationDbContext dbContext)
    {
        DbContext = dbContext;
    }

    public async Task<bool> GetMyStoredProcResult(string val1, string val2, string val3, string val4, string val5)
    {
        // input validation removed for brevity
        var p1 = new SqlParameter
        {
            ParameterName = "p1",
            DbType = System.Data.DbType.String,
            Direction = System.Data.ParameterDirection.Input,
            Value = val1
        };
        // p2 - p5 removed for brevity
        var resultParam = new SqlParameter
        {
            ParameterName = "Result",
            DbType = System.Data.DbType.Boolean,
            Direction = System.Data.ParameterDirection.Output
        };
        var sql = "EXEC sp_MyProcedure @p1, @p2, @p3, @p4, @p5, @Result OUTPUT";
        _ = await DbContext.Database.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
        return (bool)resultParam.Value;
    }
}

【问题讨论】:

    标签: asp.net-core stored-procedures entity-framework-core xunit.net asp.net-core-3.1


    【解决方案1】:

    在单元测试中,您可以假设其他部分和外部调用始终正常执行。所以你可以像这样模拟调用。一种方法是实现一些策略,将过程调用移动到具有特定实现的接口中

    interface IProcedureExecutor
    {
        void MyProcedure();
    }
    
    class DefaultProcedureExecutor: IProcedureExecutor
    {
        public void MyProcedure()
        {
            //call procedure in database
        }
    }
    
    class MockedProcedureExecutor: IProcedureExecutor
    {
        public void MyProcedure()
        {
            //do some direct data operations with in memory database
        }
    }
    

    然后在您的MyFoo 中传递IProcedureExecutor 并在单元测试期间将实例替换为模拟的实例。

    这只是一个例子。你也可以像这样模拟任何部分

    【讨论】:

    • 感谢您的回复。如果我理解正确,我将使用DefaultProcedureExecutor.MyProcedure() 换行包含ExecuteSqlRawAsync 的行,并让我的代码调用后者。我希望以某种方式模拟 DbContext.Database.ExecuteSqlRawAsync() 函数,但如果我不能这样做,那么我会考虑你的建议。
    • 再次感谢。我根据您的建议使用了解决方案。我会将我的解决方案作为单独的答案发布。
    • 您也可以尝试使用 Moq lib 并执行类似 var mock = new Mock&lt;ApplicationDbContext&gt;() 和 mock.Setup(_ =&gt; _.ExecuteSqlRawAsync()).Returns(...some result...) 之类的操作,然后将 mock.Object 传递给您的服务模拟对象
    【解决方案2】:

    我的最终解决方案是基于 Stas Petrov 给出的答案。我使用一个接口封装了对DbContext.Database.ExecuteSqlRawAsync() 的调用,该接口带有一个添加到Startup.ConfigureServices() 中的DI 的类。

    我创建了以下接口和类:

    public interface IStoredProcedureExecutor
    {
        public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters);
    }
    
    public class StoredProcedureExecutor : IStoredProcedureExecutor
    {
        public ApplicationDbContext DbContext { get; }
    
        public StoredProcedureExecutor(ApplicationDbContext dbContext)
        {
            DbContext = dbContext;
        }
    
        public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters)
        {
            return DbContext.Database.ExecuteSqlRawAsync(sql, parameters);
        }
    }
    

    在我的问题代码中,我替换了这个调用:

    _ = await DbContext.Database.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
    

    有了这个:

    _ = await StoredProcedureExecutor.ExecuteSqlRawAsync(sql, p1, p2, p3, p4, p5, resultParam);
    

    然后在测试代码中,我创建了我实例化的这个类,设置了一个合适的ReturnValue,然后插入到我正在测试的类中而不是StoredProcedureExecutor:

    class TestStoredProcedureExecutor : IStoredProcedureExecutor
    {
        public bool ReturnValue { get; set; }
    
        public Task<int> ExecuteSqlRawAsync(string sql, params object[] parameters)
        {
            foreach (var param in parameters)
            {
                var p = (SqlParameter)param;
                if (p.Direction == System.Data.ParameterDirection.Output) p.Value = ReturnValue;
            }
            return Task.FromResult(0);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-05
      • 2018-05-08
      • 1970-01-01
      • 2018-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多