UnoSD / Moq.Dapper

Moq extensions for Dapper methods.
GNU General Public License v2.0
173 stars 78 forks source link

Dapper Does not work with IDbTransaction #43

Open darthmolen opened 5 years ago

darthmolen commented 5 years ago

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.

smokedlinq commented 5 years ago

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.

UnoSD commented 5 years ago

Thanks @smokedlinq for explaining that.

ypedroo commented 3 years ago

@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).

smokedlinq commented 3 years ago

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

ypedroo commented 3 years ago

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

smokedlinq commented 3 years ago

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();
}
ypedroo commented 3 years ago

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!