简体   繁体   中英

System.Not Supported exception for mocking ExecuteSQLCommand Moq Entity Framework Core

I am trying to write a unit test for a method in my database layer. I am getting error at context.Setup for ExecuteSqlCommand :

System.NotSupportedException: 'Unsupported expression: ... => .... ExecuteSqlCommand((RawSqlString)It.IsAny<string>(), It.IsAny<object[]>()) Extension methods (here: RelationalDatabaseFacadeExtensions.ExecuteSqlCommand) may not be used in setup / verification expressions.

My method looks like this:

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

My unit test is something like this:

[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(context.Object);
        repo.InsertIntoQueue();                
    }
}

Is there a way to mock the ExecuteSQLCommand method directly?

It is not possible to mock it directly as it is an extension method.

It is possible to mock what that extension method invokes though and mock it indirectly. I had a similar case and ended up implementing EntityFrameworkCore.Testing as it's a pretty involved Moq set up.

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

This is the set up required to support both ExecuteSqlCommand and ExecuteSqlCommandAsync. executeSqlCommandResult is the integer result to return when invoked.

Note that I am passing the mocked db context to the database facade mock constructor then performing another Moq set up on the mocked database, I get away with this as I have already performed a set up on the DbContext Database property as part of mocking the db context. I have observed test success with an inline new instance so I don't think it matters, just in my case I didn't want to spin up another instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM