3

I am using the FakeItEasy library for unit testing and trying to write a unit test for a mongo update statement and verify that the FindOneAndUpdateAsync method was called using MustHaveHappened().

I have created a unit test that will fake the dbcontext class, use that to generate a collection, then call the method that will run the update statement against the fake collection. When I run the test in debug mode, it is hitting the FindOneAndUpdateAsync method, however the MustHaveHappened() result is showing that the method did not run against the faked collection.

Is there a way to use FakeItEasy to detect that the FindOneAndUpdateAsync method ran when the collection it is being run against is also being faked?

Error Message from Failed Test

Assertion failed for the following call: Any call made to the fake object. where x => (x.Method.Name == "FindOneAndUpdateAsync") Expected to find it once or more but didn't find it among the calls: 1: MongoDB.Driver.IMongoCollection`1[ConsoleApp3.Fruit].WithWriteConcern(writeConcern: { w : "majority" })

Unit Test Using FakeItEasy

    [Fact]
    public async Task TestFruitUpdate()
    {
        IDBContext fakeDbContext = A.Fake<DBContext>();
        var fakeMongoCollection = A.Fake<IMongoCollection<Fruit>>();
        var fruitEntity = new Fruit()
        {
            Id = new MongoDB.Bson.ObjectId(),
            Name = "Apple",
            Price = 2.0,
            Quantity = 3,
        };

        A.CallTo(() => fakeDbContext.GetCollection<Fruit>()).Returns(fakeMongoCollection);
        A.CallTo(fakeDbContext).WithReturnType<IMongoCollection<Fruit>>().Returns(fakeMongoCollection);

        var repository = new FruitRepository(fakeDbContext);

        await repository.UpdateFruitQuantityAsync(fruitEntity);

        A.CallTo(fakeMongoCollection).Where(x => x.Method.Name == "FindOneAndUpdateAsync").MustHaveHappened();
    }

Interface for DBContext

public interface IDBContext
{
    IMongoClient Client { get; }

    IMongoDatabase Database { get; }

    IMongoCollection<TDocument> GetCollection<TDocument>()
        where TDocument : Fruit;
}

Fruit Entity

public class Fruit
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
    public int Quantity { get; set; }
}

Fruit Repository Class

class FruitRepository
{
    private readonly IDBContext _dbContext;

    public FruitRepository(IDBContext dBContext) =>
        _dbContext = dBContext;

    public virtual Task UpdateFruitQuantityAsync(Fruit fruitEntity) =>
        _dbContext.GetCollection<Fruit>()
            .WithWriteConcern(WriteConcern.WMajority)
            .FindOneAndUpdateAsync(
                Builders<Fruit>.Filter.Eq(j => j.Id, fruitEntity.Id),
                Builders<Fruit>.Update.Set(j => j.Quantity, fruitEntity.Quantity)
             );
}
J_Xlin
  • 73
  • 1
  • 10

1 Answers1

3

The problem is that the result of WithWriteConcern is not the fakeMongoCollection. That's why FakeItEasy didn't see the FindOneAndUpdateAsync call, even though you saw that call made on something when you were debugging. Sometimes in these confusing cases, it's worthwhile checking the identity (ReferenceEquals) of the objects in the tests.

A new Fake is used because you're configuring fakeDbContext twice. Your second call should configure the fakeMongoCollection to return itself when it's asked for an IMongoCollection<Fruit>:

A.CallTo(fakeDbContext).WithReturnType<IMongoCollection<Fruit>>()
    .Returns(fakeMongoCollection);

A.CallTo(fakeMongoCollection).WithReturnType<IMongoCollection<Fruit>>()
     .Returns(fakeMongoCollection);
Blair Conrad
  • 233,004
  • 25
  • 132
  • 111