首页 > 解决方案 > 单元测试 MongoDB.Driver dotnet core

问题描述

我们正在使用命令/查询模式,其中实现对 MongoDB 如何工作有详细的了解,我们想为此编写一个测试。IMongoCollection<CarDocument>在确保发送正确的过滤器的同时模拟 MongoDbFind是非常具有挑战性的。我们正在使用 .NET core 2.1 和 MongoDB.Driver v2.7.2

using MongoDB.Driver;

namespace Example
{


    public class SomeMongoThing : ISomeMongoThing
    {
        public IMongoCollection<CarDocument> GetCars()
        {
            var client = new MongoClient("ConnectionString");
            var database = client.GetDatabase("DatabaseName");
            return database.GetCollection<CarDocument>("CollectionName");
        }
    }

    public interface ISomeMongoThing
    {
        IMongoCollection<CarDocument> GetCars();
    }

    public class GetCarQuery
    {
        private readonly ISomeMongoThing someMongoThing;

        public GetCarQuery(ISomeMongoThing someMongoThing)
        {
            this.someMongoThing = someMongoThing;
        }

        public CarDocument Query(string aKey)
        {
            var schedules = someMongoThing.GetCars();

            var match = schedules.Find(x => x.AKey == aKey);
            return match.Any() ? match.First() : this.GetDefaultCar(schedules);
        }

        private CarDocument GetDefaultCar(IMongoCollection<CarDocument> schedules)
        {
            return schedules.Find(x => x.AKey == "Default").First();
        }
    }
}

我们在这里有一个测试,但我们无法编写一个测试来检查是否使用了正确aKey的过滤器,这意味着如果我们x => x.AKey == "hello"在实现中使用过滤器,测试应该会失败。即使代码通过.Find(x => true)了测试。

using System.Collections.Generic;
using System.Threading;
using MongoDB.Driver;
using Moq;
using NUnit.Framework;

namespace Example
{
    public class GetCarQueryTest
    {
        [Test]
        public void ShouldGetByApiKey()
        {
            var mockCarDocument = new CarDocument();
            var aKey = "a-key";

            var result = Mock.Of<IAsyncCursor<CarDocument>>(x =>
                x.MoveNext(It.IsAny<CancellationToken>()) == true
                && x.Current == new List<CarDocument>() { mockCarDocument });
            var cars = Mock.Of<IMongoCollection<CarDocument>>(x => x.FindSync(
                It.IsAny<FilterDefinition<CarDocument>>(),
                It.IsAny<FindOptions<CarDocument, CarDocument>>(),
                It.IsAny<CancellationToken>()) == result);

            var someMongoThing = Mock.Of<ISomeMongoThing>(x => x.GetCars()() == cars);
            var getCarQuery = new GetCarQuery(someMongoThing);

            var car = getCarQuery.Query(aKey);

            car.Should().Be(mockCarDocument);
        }
    }
}

您将如何测试提供的代码?如果在事物之间进行抽象SomeMongoThingGetCarQuery有所帮助,我们愿意接受建议。这个想法是查询了解 MongoDb 以便能够利用 MongoDb 客户端的功能,并且查询的用户不必关心。

标签: .netmongodb.net-coretddmoq

解决方案


在我看来,这似乎是作为XY 问题一部分的泄漏抽象。

来自评论

无论您如何抽象代码的某些部分来处理 MongoCollection,您如何测试该类?

我不会测试那个课程。最终包装集合的类不需要进行单元测试,因为它只是第 3 方关注点的包装器。MongoCollection 的开发人员会测试他们的代码以供发布。我将整个 mongo 依赖视为第 3 方实施问题。

看看下面的替代设计

public interface ICarRepository {
    IEnumerable<CarDocument> GetCars(Expression<Func<CarDocument, bool>> filter = null);
}

public class CarRepository : ICarRepository {
    private readonly IMongoDatabase database;

    public CarRepository(Options options) {
        var client = new MongoClient(options.ConnectionString);
        database = client.GetDatabase(options.DatabaseName);
    }

    public IEnumerable<CarDocument> GetCars(Expression<Func<CarDocument, bool>> filter = null) {
        IMongoCollection<CarDocument> cars = database.GetCollection<CarDocument>(options.CollectionName);
        return filter == null ? cars.AsQueryable() : (IEnumerable<CarDocument>)cars.Find(filter).ToList();
    }
}

为简单起见,我重命名了一些依赖项。它应该是不言自明的。所有与 Mongo 相关的关注点都封装在它自己的关注点中。存储库可以根据需要利用 MongoDb 客户端的所有功能,而不会泄露对第 3 方关注点的不必要依赖。

可以相应地重构依赖查询类

public class GetCarQuery {
    private readonly ICarRepository repository;

    public GetCarQuery(ICarRepository repository) {
        this.repository = repository;
    }

    public CarDocument Query(string aKey) {
        var match = repository.GetCars(x => x.AKey == aKey);
        return match.Any()
            ? match.First()
            : repository.GetCars(x => x.AKey == "Default").FirstOrDefault();
    }
}

现在可以在一个孤立的单元测试中简单地模拟上述类的快乐路径

public class GetCarQueryTest {
    [Test]
    public void ShouldGetByApiKey() {
        //Arrange
        var aKey = "a-key";
        var mockCarDocument = new CarDocument() {
            AKey = aKey
        };

        var data = new List<CarDocument>() { mockCarDocument };

        var repository = new Mock<ICarRepository>();

        repository.Setup(_ => _.GetCars(It.IsAny<Expression<Func<CarDocument, bool>>>()))
            .Returns((Expression<Func<CarDocument, bool>> filter) => {
                return filter == null ? data : data.Where(filter.Compile());
            });

        var getCarQuery = new GetCarQuery(repository.Object);

        //Act
        var car = getCarQuery.Query(aKey);

        //Assert
        car.Should().Be(mockCarDocument);
    }
}

测试与 Mongo 相关的实际问题需要进行集成测试,您将在其中连接到实际源以确保预期行为。


推荐阅读