Moq.Dapper icon indicating copy to clipboard operation
Moq.Dapper copied to clipboard

Dapper Does not work with IDbTransaction

Open darthmolen opened this issue 5 years ago • 7 comments

When trying to moq with moq.dapper using an IDbTransaction, It seems to call but won't return the "returns".

Using the same call, I pass a null to the setup and the actual call, the returns works.

darthmolen avatar Jun 06 '19 18:06 darthmolen

Try this:

var mock = new Mock<DbConnection>();
mock.As<IDbConnection>()
            .Setup(x => x.BeginTransaction())
            .Returns(() => new Mock<DbTransaction>().Object);
var connection = (IDbConnection)mock.Object;

using (var transaction = connection.BeginTransaction())
{
    transaction.Commit();
}

Best I understand, this is because DbConnection doesn't let you override BeginTransaction but you can with a new implementation of IDbConnection for the mock, you just have to remember to cast the mocked object to IDbConnection otherwise it will try to use the DbConnection.BeginTransaction method which won't work because the protected method isn't implemented as Moq returns default for abstract methods, e.g. default(DbTransaction) == null.

smokedlinq avatar Aug 20 '19 02:08 smokedlinq

Thanks @smokedlinq for explaining that.

UnoSD avatar Aug 20 '19 17:08 UnoSD

@smokedlinq you were able to do this in a functional way? I tried many solutions and found yours but my mock threw on SQL mapper when assigned the transaction mock to the command.

    var mock = new Mock<DbConnection>();
    mock.As<IDbConnection>()
.Setup(x => x.BeginTransaction())
.Returns(()=> transactionMock.Object);
    var connection =(IDbConnection)mock.Object;
    mock.SetupDapperAsync(c => c.ExecuteAsync(It.IsAny<string>(), null, null, null, null))
.ReturnsAsync(0);
    _connectionFactoryMock.Setup(e => e.CreateConnectionOpened()).Returns(connection);

In my scenario, we have to have this connection factory returning a connection (company policy).

ypedroo avatar Aug 26 '21 22:08 ypedroo

Does the SetupDapperAsync not take a transaction then? Did you try doing the setup before you generated a proxy on mock.Object?

smokedlinq avatar Aug 27 '21 00:08 smokedlinq

For your first question I think so, and the second one yes it was my first attempt.

ypedroo avatar Aug 27 '21 00:08 ypedroo

Here is a quick example I put together ... is this close to what you are testing ... it's working on my sample app ...

using Dapper;
using FluentAssertions;
using Moq;
using Moq.Dapper;
using System.Data;
using System.Data.Common;
using System.Threading.Tasks;

var transactionMock = new Mock<DbTransaction>();
var connectionFactoryMock = new Mock<IConnectionFactory>();
var mock = new Mock<DbConnection>();

mock.As<IDbConnection>()
    .Setup(x => x.BeginTransaction())
    .Returns(() => transactionMock.Object);

mock.SetupDapperAsync(c => c.ExecuteAsync(It.IsAny<string>(), It.IsAny<IDbTransaction>(), null, null, null))
    .ReturnsAsync(0);

var connection = (IDbConnection)mock.Object;
connectionFactoryMock.Setup(e => e.CreateConnectionOpened()).Returns(connection);

var result = await ExecuteQueryAsync(connectionFactoryMock.Object);

result.Should().Be(0);

async Task<int> ExecuteQueryAsync(IConnectionFactory factory)
{
    using var connection = factory.CreateConnectionOpened();
    using var transaction = connection.BeginTransaction();
    var result = await connection.ExecuteAsync("INSERT INTO FU VALUES (1)", transaction: transaction);
    transaction.Commit();
    return result;
}

public interface IConnectionFactory
{
    IDbConnection CreateConnectionOpened();
}

smokedlinq avatar Aug 27 '21 01:08 smokedlinq

hey @smokedlinq i had to travel and didn't tested until today sorry for the late response, but worked like a charm thank you so much!

ypedroo avatar Aug 30 '21 22:08 ypedroo