Как выполнить модульное тестирование функции в. NET Core 3.1, которая использует ExecuteSqlRawAsyn c для вызова хранимой процедуры? - PullRequest
0 голосов
/ 01 апреля 2020

Я пишу API в ASP. NET Core 3.1, используя 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;
    }
}

Ответы [ 2 ]

1 голос
/ 01 апреля 2020

В модульном тестировании вы можете предположить, что другие части и внешние вызовы всегда выполняются нормально. Таким образом, вы можете издеваться над звонками, как это. Одним из способов является реализация некоторой стратегии с перемещением вызовов процедур в интерфейс с указанными c реализациями

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
    }
}

Затем передайте IProcedureExecutor в ваш MyFoo и замените экземпляр на поддельный во время модульного тестирования.

Это всего лишь пример. Вы также можете издеваться над любыми частями, подобными этой

0 голосов
/ 01 апреля 2020

Мое окончательное решение основано на ответе Стаса Петрова. Я обернул вызов к DbContext.Database.ExecuteSqlRawAsync(), используя интерфейс с классом, который добавляется к DI в Startup.ConfigureServices().

. Я создал следующий интерфейс и класс:

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);
    }
}
...