Dapper icon indicating copy to clipboard operation
Dapper copied to clipboard

Issue mocking(moq) ExecuteAsync for unit test(xunit)

Open chrischunghoneywell opened this issue 4 years ago • 4 comments

a moq of ExecuteAsync in my unit testing is throwingInnerException = {"Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand"}. Execute seems to work fine.

private  void SetupExecuteAsync ()
        {
            var dataParameterCollection = new Mock<IDataParameterCollection>();
            dbCommandMock.Setup(x => x.Parameters).Returns(dataParameterCollection.Object);
            dbCommandMock.Setup(x => x.ExecuteNonQuery()).Returns(It.IsAny<int>());
            dbConnectionMock.Setup(s => s.CreateCommand()).Returns(dbCommandMock.Object);
        }

I am mocking DbCommand object and Execute and ExecuteAsync seem to have the same parameters so I'm not sure why I am getting this error.

return connection.Execute(sql, dbParams, commandType: commandType);
 vs
return await connection.ExecuteAsync(sql, dbParams, commandType: commandType).ConfigureAwait(false);

chrischunghoneywell avatar May 12 '21 07:05 chrischunghoneywell

Can you share the actual type hierarchy of what's being mocked?

NickCraver avatar May 12 '21 11:05 NickCraver

I just ran into this problem on my project too.

The test performs differently for connection.Execute(...) and connection.ExecuteAsync(...) because the async version calls this method:

private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action<IDbCommand, object> paramReader)
{
  if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand)
  {
    return dbCommand;
  }
  else
  {
    throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand");
  }
}

Notice that the above method is insuring that the generated command derives from the base class System.Data.Common.DbCommand. That will likely not be the case for your mocked implementation of a System.Data.Common.IDbCommand.

WallaceKelly avatar May 17 '21 15:05 WallaceKelly

In case it helps anyone, I ran into this problem and ended up sub-classing what Dapper is expecting, and then delegating calls to NSubstitute:

Class:

private class DbCommandMock : DbCommand
{
    private readonly IDbCommand _dbCommandStub;

    public DbCommandMock(IDbCommand dbCommandStub)
    {
        _dbCommandStub = dbCommandStub;
    }

    public override void Cancel() => _dbCommandStub.Cancel();

    public override int ExecuteNonQuery() => _dbCommandStub.ExecuteNonQuery();

    public override object ExecuteScalar() => _dbCommandStub.ExecuteScalar();

    public override void Prepare() => _dbCommandStub.Prepare();

    public override string CommandText { get; set; }
    public override int CommandTimeout { get; set; }
    public override CommandType CommandType { get; set; }
    public override UpdateRowSource UpdatedRowSource { get; set; }
    protected override DbConnection DbConnection { get; set; }
    protected override DbParameterCollection DbParameterCollection { get; }
    protected override DbTransaction DbTransaction { get; set; }
    public override bool DesignTimeVisible { get; set; }

    protected override DbParameter CreateDbParameter() => new MySqlParameter();

    protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => throw new NotImplementedException();
}

Test setup:

var dbCommand = new DbCommandMock(Substitute.For<IDbCommand>());
var dbConnection = Substitute.For<IDbConnection>();
dbConnection.CreateCommand().Returns(dbCommand);

jchoca avatar Sep 09 '21 23:09 jchoca

Same problem. Solution from @jchoca doesn't work for me.

maxarendsen avatar May 13 '22 11:05 maxarendsen