单元测试MongoDB.Driver dotnet核心

时间:2019-02-26 14:55:21

标签: .net mongodb .net-core tdd moq

我们使用的是Command / Query模式,其中的实现具有有关MongoDB如何工作的详细知识,我们希望为此编写测试。在模拟MongoDb <bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl" lazy-init="true"> **<property name="maxAuthenticationAge" value="86400"/>** <property name="responseSkew" value="600"/> <!-- 10 minutes --> <property name="maxAssertionTime" value="6000"/> </bean> <bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint" lazy-init="true"> <property name="defaultProfileOptions"> <bean class="org.springframework.security.saml.websso.WebSSOProfileOptions"> <property name="includeScoping" value="false"/> <!-- Default: true. When true request will include Scoping element. --> **<property name="forceAuthN" value="true"/>** <!-- Default: false. When true IDP is required to re-authenticate user and not rely on previous authentication events --> <property name="passive" value="false"/> <!-- Default: false. Sets whether the IdP should refrain from interacting with the user during the authentication process. --> </bean> </property> 的同时确保发送正确的IMongoCollection<CarDocument>过滤器非常具有挑战性。我们正在使用.NET core 2.1和MongoDB.Driver v2.7.2

Find

我们在这里有一个测试,但是我们无法编写测试以检查是否使用了正确的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之间进行抽象有帮助,我们欢迎您提出建议。想法是查询具有有关MongoDb的知识,以便能够利用MongoDb客户端的功能,并且查询的用户不必关心。

1 个答案:

答案 0 :(得分:0)

在我看来,这是XY problem中一部分的泄漏抽象。

来自评论

  

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

我不会测试那堂课。最终包装集合的类不需要进行单元测试,因为它只是第三者关注的包装器。 MongoCollection的开发人员将测试其代码的发布。我认为整个mongo依赖关系都是第三方实现的关注点。

看看下面的替代设计

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客户端的所有功能,而不会泄漏对第三方的担忧。

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

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相关的实际问题需要进行集成测试,在该测试中,您将连接到实际资源以确保预期的行为。