Как смоделировать коллекцию MongoDb, чтобы вернуть некоторые фиктивные данные в C #? - PullRequest
0 голосов
/ 12 мая 2019

Я пытаюсь написать несколько модульных тестов для сценария, в котором я пишу данные в 2 разных коллекциях в рамках одной транзакции. Функциональность прекрасно работает во время ручного тестирования (например, принудительное создание исключения после вызова метода ReplaceOneAsync внутри блока try...catch). И я хочу проверить, что если одна из операций записи завершится неудачно, вся транзакция будет откатана. Ниже приведен мой пример кода:

Мой DbContext:

public class DbContext : IDbContext
{
    private readonly IMongoDatabase _database;
    // construcor code to populate _database
    // code for other collections
    public IMongoDatabase Database => _database;

    public IMongoCollection<CustomerDoc> Customers => _database.GetCollection<CustomerDoc>("Customers");
    public IMongoCollection<BusinessLogDoc> BusinessLogs => _database.GetCollection<BusinessLogDoc>("BusinessLogs");
}

Метод работы с моей базой данных:

public async Task<GetCustomerResponse> UpSertCustomerAsync(UpSertCustomerRequest request)
{
    // some other code ...
    var existing = await _dbContext.Customers.Find(p => p.Name == request.Name).FirstOrDefaultAsync();
    if (existing != null && existing.Id != request.Id)
    {
        throw new Exception($"Name:{request.Name} already used.");
    }
    // code to populate customer and businessLog object
    // ...
    var session = await _dbContext.Database.Client.StartSessionAsync();
    session.StartTransaction();

    try
    {
        await _dbContext.Customers.ReplaceOneAsync(session, doc => doc.Id == request.Id, customer, new UpdateOptions { IsUpsert = true });
        await _dbContext.BusinessLogs.InsertOneAsync(session, businessLog);
        session.CommitTransaction();
    }
    catch (Exception ex)
    {
        session.AbortTransaction();
        throw new Exception("Database operation failed, rolling back.");
    }
    // code to return response
}   

Вы заметили, что в приведенном выше коде я сначала проверяю, существует ли клиент. Ниже приведен код моего модульного теста:

Модульный тест

public interface IFakeMongoCollection : IMongoCollection<BsonDocument>
{
    IFindFluent<BsonDocument, BsonDocument> Find(FilterDefinition<BsonDocument> filter, FindOptions options);
    IFindFluent<BsonDocument, BsonDocument> Project(ProjectionDefinition<BsonDocument, BsonDocument> projection);
    IFindFluent<BsonDocument, BsonDocument> Skip(int skip);
    IFindFluent<BsonDocument, BsonDocument> Limit(int limit);
    IFindFluent<BsonDocument, BsonDocument> Sort(SortDefinition<BsonDocument> sort);
}

public class MyControllerTests
{
    private IOptions<MongoClientSettings> _mongoSettings;
    private Mock<IFakeMongoCollection> _fakeMongoCollection;
    private Mock<IMongoDatabase> _fakeMongoDatabase;
    private Mock<IMyDbContext> _fakeMongoContext;
    private Mock<IFindFluent<BsonDocument, BsonDocument>> _fakeCollectionResult;

    private Mock<IMongoCollection<CustomerDoc>> _customers;
    private Mock<IMongoCollection<BusinessLogDoc>> _logs;

    private MyController _controller;

    public MyControllerTests()
    {
        _fakeMongoCollection = new Mock<IFakeMongoCollection>();
        _fakeCollectionResult = new Mock<IFindFluent<BsonDocument, BsonDocument>>();
        _fakeMongoDatabase = new Mock<IMongoDatabase>();

        _customers = new Mock<IMongoCollection<CustomerDoc>>();
        _logs = new Mock<IMongoCollection<BusinessLogDoc>>();
    }

    private void SetupMockCollection(string collectionName)
    {
        _customers.Object.InsertOne(new CustomerDoc
        {
            Id = ObjectId.GenerateNewId()
        });

        switch (collectionName)
        {
            case "Customers":
                _fakeMongoDatabase.Setup(_ => _.GetCollection<CustomerDoc>(collectionName, It.IsAny<MongoCollectionSettings>()))
                    .Returns(_customers.Object);
                break;
            case "BusinessLogs":
                _fakeMongoDatabase.Setup(_ => _.GetCollection<BusinessLogDoc>(collectionName, It.IsAny<MongoCollectionSettings>())).Returns(_logs.Object);
                break;
            default:
                _fakeMongoDatabase.Setup(_ => _.GetCollection<BsonDocument>(collectionName, It.IsAny<MongoCollectionSettings>()))
                    .Returns(_fakeMongoCollection.Object);
                break;
        }

        _fakeMongoContext = new Mock<IMyDbContext>();
        _fakeMongoContext.Setup(t => t.Database).Returns(_fakeMongoDatabase.Object);
        _fakeMongoContext.Setup(t => t.BusinessLogs).Returns(_logs.Object);
    }

    [Fact]
    public void Test()
    {
        SetupMockCollection("Customers");

        var context = _fakeMongoContext.Object;
        var db = _fakeMongoDatabase.Object;

        var collection = db.GetCollection<CustomerDoc>("Customers");

        // how to create an object of type IMongoCollection<CustomerDoc> 
        // how to mock collection.Find<CustomerDoc> so that it returns the dummy data

        // below line returns mocked object without the data I have added inside SetupMockCollection method
        var docs = collection.Find<CustomerDoc>(t => t.Id != null);

        int count = 0;
        try
        {
            count = docs.ToList().Count;//throws exception
        }
        catch (Exception)
        {
            count = 0;
        }
        Assert.NotNull(docs);
        Assert.Equal(1, count);
    }
}  

Наряду с насмешкой над методом Find мне нужно также смоделировать методы ReplaceOneAsync и InsertOneAsync, чтобы я мог заставить любой из этих методов вызвать исключение в моем тесте и убедиться, что session.AbortTransaction(); работает, как ожидалось (или, по крайней мере, получил вызов). На некоторые другие вопросы SO, я видел, что люди предлагают НЕ тестировать функции MongoDB , но в моем сценарии мне нужно проверить, что в базе данных нет противоречивых данных. Пожалуйста, предложите.

Я использую драйвер MongoDB для .Net 2.7.0, c #, xUnit.

1 Ответ

0 голосов
/ 13 мая 2019

Я думаю, что вы лаете под неправильным деревом.

Вам нужен интеграционный тест, а не юнит-тест.На вашем месте я бы забыл об «модульном тестировании» этой части, потому что у вас просто нет способа его модульного тестирования.Вместо этого напишите интеграционный тест, сделайте так, чтобы он провалился с некоторыми недействительными данными, а затем посмотрите, что произойдет.

Вы можете продолжать идти по своему текущему пути и тратить время на насмешки над всем, но в конце концов вы не получите никаких результатов.значение, так как вам нужно проверить фактическую базу данных, чтобы увидеть, что произошло, и никакое количество насмешек не даст вам этого.

Итак, подготовьте несколько реальных запросов для этого, похоже, у вас есть API, возможно, используйте что-то вродеПочтальон, чтобы бросить некоторые реальные испытания в этом.

...