Модульное тестирование MongoDB. Драйвер dotnet core - PullRequest
0 голосов
/ 26 февраля 2019

Мы используем шаблон Command / Query, где реализация подробно знакома с тем, как работает MongoDB, и мы хотим написать тест для этого.Дразнить MongoDb IMongoCollection<CarDocument>, а также проверять правильность отправки фильтра Find довольно сложно.Мы используем .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);
        }
    }
}

Как бы вы проверили предоставленный код?Если абстракция между SomeMongoThing и GetCarQuery помогает вещам, которые мы открыты для предложений.Идея состоит в том, что Query обладает знаниями о MongoDb, чтобы использовать возможности клиента MongoDb, и что пользователю запроса это не нужно.

1 Ответ

0 голосов
/ 27 февраля 2019

Это, по моему мнению, является утечкой абстракции как часть проблемы XY .

Из комментариев

Независимо от того, как вы абстрагируете некоторыечасть вашего кода будет обрабатывать MongoCollection, как вы тестируете этот класс?

Я бы не стал тестировать этот класс.Класс, который в конечном итоге обернет коллекцию, не нужно будет подвергать модульному тестированию, поскольку он будет просто оберткой для сторонней организации.Разработчики MongoCollection протестировали бы их код для выпуска.Я вижу всю зависимость монго как стороннюю реализацию реализации.

Взгляните на следующий альтернативный дизайн

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();
    }
}

Для простоты я переименовал некоторые из зависимостей.Это должно быть само за себя.Все проблемы, связанные с Монго, заключены в его собственном беспокойстве.Хранилище может использовать всю мощь клиента MongoDb по мере необходимости без утечки ненужных зависимостей от сторонних задач.

Зависимый класс запросов может быть соответственно реорганизован

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, потребуется интеграционный тест, в котором вы будете подключаться к фактическому источнику для обеспечения ожидаемого поведения.

...