ExecuteSQLCommand Moq实体框架核心的System.Not Supported异常

时间:2019-07-18 22:00:04

标签: c# unit-testing entity-framework-core moq

我正在尝试为数据库层中的方法编写单元测试。我在context.Setup遇到ExecuteSqlCommand的错误:

  

System.NotSupportedException:'不支持的表达式:... => .... ExecuteSqlCommand((RawSqlString)It.IsAny<string>(), It.IsAny<object[]>())扩展方法(此处:RelationalDatabaseFacadeExtensions.ExecuteSqlCommand)不能在设置/验证表达式中使用。

我的方法如下:

public void InsertIntoQueue()
{           
    context.Database
           .ExecuteSqlCommand(" EXEC SP_Name param1",
                              new SqlParameter("param1", param1.value));           
}

我的单元测试是这样的:

[Fact]
[Trait("TestCategory", "Unit")]
public async void Test1_Ok()
{
    var context = new Mock<Context>();
    {
        context.Setup(x => x.Database.ExecuteSqlCommand(It.IsAny<string>(), It.IsAny<object[]>())).Returns(1);
        var repo = GetDBRepository(amsContext.Object);
        repo.InsertIntoQueue();                
    }
}

是否可以直接模拟ExecuteSQLCommand方法?

1 个答案:

答案 0 :(得分:0)

由于它是扩展方法,因此无法直接模拟它。

可以模拟该扩展方法调用的内容并间接对其进行模拟。我有一个类似的案例,并最终实施了EntityFrameworkCore.Testing,因为Moq的设置相当复杂。

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

这是同时支持ExecuteSqlCommand和ExecuteSqlCommandAsync所需的设置。 executeSqlCommandResult是调用时要返回的整数结果。

请注意,我正在将模拟的db上下文传递给数据库外观模拟构造函数,然后在模拟的数据库上执行另一次Moq设置,由于我已经在DbContext Database属性上执行了设置,因此我逃避了此操作作为模拟数据库上下文的一部分。我已经观察到内联新实例的测试成功,所以我认为这并不重要,就我而言,我不想启动另一个实例。