Moq实体框架执行SQLCommand

本文关键字:SQLCommand 执行 框架 实体 Moq | 更新日期: 2023-09-27 18:28:27

我读到,当使用moq时,不能模拟非虚拟函数。我也读到这应该是可能的。。这是真的吗?如果是这样,那么我想模拟以下查询:

DatabaseContext.Database.ExecuteSqlCommand(updateQuery, newValue);

我在测试中覆盖上下文,因此

DAL.Context.DatabaseContext = mockContext.Object;

我已经尝试过这种设置,但似乎查询仍然与我的常规数据库相反

mockContext.Setup(c => c.Set<AppSalesAndResult>()).Returns(mockBudgetData.Object);

有什么想法吗,也许executesql命令可以用其他东西代替,这样上面的行就可以捕捉到集合的任何udpate了吗?在一次更新多行时,由于性能原因,我使用executesql命令。常规EF 太慢

更新:

阅读以下文章,如何Moq实体框架SqlQuery调用,我想知道类似的实现是否适用于ExecuteSQLCommand。。。

Moq实体框架执行SQLCommand

为了能够模拟ExecuteSqlCommand,我所做的是在我的DbContext继承中创建相同的方法,但这次是虚拟的,并调用DataBase.ExecuteSqlCommand

public class MyDbContext : DbContext
{
    public virtual int ExecuteSqlCommand(string sql, params object[] parameters)
    {
        return Database.ExecuteSqlCommand(sql, parameters);
    }
    public virtual int ExecuteSqlCommand(TransactionalBehavior transactionalBehavior, string sql, params object[] parameters)
    {
        return Database.ExecuteSqlCommand(transactionalBehavior, sql, parameters);
    }

然后我更改了我的业务代码,调用这个创建的方法(非数据库方法):

DatabaseContext.ExecuteSqlCommand(updateQuery, newValue);

然后,它工作

可以模拟ExecuteSqlCommand。然而,这并不简单。

作为一个扩展方法,您必须模拟内部。它最终创建一个RawSqlCommand并在IRelationalCommand上调用ExecuteNonQuery。随着扩展方法创建新对象来完成实际工作,以及没有RawSqlCommandDatabaseFacade的接口,它变得更加复杂,您必须模拟具体的类。

我最终写了EntityFrameworkCore。测试是因为周围没有其他东西可以做所有的mock(FromSql、ExecuteSqlCommand、DbQuery,内存中提供程序无法做的关系型东西)。节省一些时间,因为涉及到模拟,如果我在寻找时有一个现有的包,我肯定会使用它。

如果您确实想自己滚动ExecuteSqlCommand/ExecuteSqlCommandAsync的模拟设置如下所示:

var relationalCommandMock = new Mock<IRelationalCommand>();
relationalCommandMock
    .Setup(m => m.ExecuteNonQuery(It.IsAny<IRelationalConnection>(), It.IsAny<IReadOnlyDictionary<string, object>>()))
    .Returns((IRelationalConnection providedConnection, IReadOnlyDictionary<string, object> providedParameterValues) => executeSqlCommandResult);
relationalCommandMock
    .Setup(m => m.ExecuteNonQueryAsync(It.IsAny<IRelationalConnection>(), It.IsAny<IReadOnlyDictionary<string, object>>(), It.IsAny<CancellationToken>()))
    .Returns((IRelationalConnection providedConnection, IReadOnlyDictionary<string, object> providedParameterValues, CancellationToken providedCancellationToken) => Task.FromResult(executeSqlCommandResult));
var relationalCommand = relationalCommandMock.Object;
var rawSqlCommandMock = new Mock<RawSqlCommand>(MockBehavior.Strict, relationalCommand, new Dictionary<string, object>());
rawSqlCommandMock.Setup(m => m.RelationalCommand).Returns(relationalCommand);
rawSqlCommandMock.Setup(m => m.ParameterValues).Returns(new Dictionary<string, object>());
var rawSqlCommand = rawSqlCommandMock.Object;
var rawSqlCommandBuilderMock = new Mock<IRawSqlCommandBuilder>();
rawSqlCommandBuilderMock
    .Setup(m => m.Build(It.IsAny<string>(), It.IsAny<IEnumerable<object>>()))
    .Returns((string providedSql, IEnumerable<object> providedParameters) => rawSqlCommand);
var rawSqlCommandBuilder = rawSqlCommandBuilderMock.Object;
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock.Setup(m => m.GetService(It.Is<Type>(t => t == typeof(IConcurrencyDetector)))).Returns((Type providedType) => Mock.Of<IConcurrencyDetector>());
serviceProviderMock.Setup(m => m.GetService(It.Is<Type>(t => t == typeof(IRawSqlCommandBuilder)))).Returns((Type providedType) => rawSqlCommandBuilder);
serviceProviderMock.Setup(m => m.GetService(It.Is<Type>(t => t == typeof(IRelationalConnection)))).Returns((Type providedType) => Mock.Of<IRelationalConnection>());
var serviceProvider = serviceProviderMock.Object;
var databaseFacadeMock = new Mock<DatabaseFacade>(MockBehavior.Strict, mockedDbContext);
databaseFacadeMock.As<IInfrastructure<IServiceProvider>>().Setup(m => m.Instance).Returns(serviceProvider);
var databaseFacade = databaseFacadeMock.Object;
Mock.Get(mockedDbContext).Setup(m => m.Database).Returns(databaseFacade);

executeSqlCommandResult是要返回的预期整数。您可以使用回调来应用sql命令对数据源执行的任何操作。

请注意,我将模拟数据库上下文传递给数据库facade模拟构造函数,然后在模拟数据库上执行另一个Moq设置,我可以逃脱惩罚,因为我已经在DbContext database属性上执行了一个设置,作为模拟数据库上下文的一部分。我观察到一个内联新实例的测试成功了,所以我认为这并不重要,只是在我的情况下,我不想旋转另一个实例