webentwicklung-frage-antwort-db.com.de

Wie kann ich DocumentClient für DocumentDb-Komponententests verspotten?

Mit dem neuen CosmosDb-Emulator habe ich eine Art Repository für grundlegende Documentdb-Operationen erhalten. Dieses Repository wird in andere Klassen injiziert. Ich wollte eine grundlegende Abfrage Unit-Test.

public class DocumentDBRepository<T> where T : class
{
 //Details ommited...

    public IQueryable<T> GetQueryable()
    {
        return _client.CreateDocumentQuery<T>(
            UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId),
            new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true });
    }

    public async Task<IEnumerable<T>> ExecuteQueryAsync(IQueryable<T> query)
    {
        IDocumentQuery<T> documentQuery = query.AsDocumentQuery();
        List<T> results = new List<T>();
        while (documentQuery.HasMoreResults)
        {
            results.AddRange(await documentQuery.ExecuteNextAsync<T>());
        }

        return results;
    }


}

Dieses Repository benötigt einen Dokument-Client, der ebenfalls in den Konstruktor eingefügt wird.

public DocumentDBRepository(string databaseId, IDocumentClient client)
{
    _client = client;
    _databaseId = databaseId;
    _collectionId = GetCollectionName();
}

Mein ursprünglicher Ansatz war es, den CosmosDb-Emulator zu verwenden, aber dafür musste der Emulator ausgeführt werden, was mir nicht gefällt, und die Tests wurden langsamer.

Mein zweiter Ansatz war, zu versuchen, einen Schein des Dokumenten-Clients zu verwenden.

var data = new List<MyDocumentClass>
{
    new MyDocumentClass{ Description= "BBB" },
    new MyDocumentClass{ Description= "ZZZ" },

}
.AsQueryable()
.OrderBy(q => q.Description);
var client = new Mock<IDocumentClient>();
client.As<IDocumentClient>()
    .Setup(foo => foo.CreateDocumentQuery<MyDocumentClass>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
    .Returns(data);

DocumentDBRepository<MyDocumentClass> repo= new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object);

Der Code, der dieses Repository verwendet, funktioniert folgendermaßen:

var query = _documentsRepository.GetQueryable()
                .Where(d => d.Description = description)
                .OrderByDescending(d => d.description)
                .Take(100);
//Execute query async fails. 
var result = await _documentsRepository.ExecuteQueryAsync(query);

Dies schlägt fehl, weil das Repository versucht, das IQueryable in ein IDocumentQuery -Objekt zu konvertieren, was für DocumentDb api sehr spezifisch ist (siehe Methode ExecuteQueryAsync oben). Später führt es die Methode HasMoreResults aus. Selbst wenn ich .asDocumentQuery() verspotte, um mein Objekt zurückzugeben, weiß ich nicht, wie ich ein Ergebnis für HasMoreResults und ExecuteNextAsync liefern kann, damit es Sinn ergibt für meine Unit-Tests.

Meine dritte Möglichkeit wäre, mein Repository anstelle des DocumentClient-Objekts direkt zu verspotten. Wäre, denke ich, einfacher, aber ich würde nicht viel von der DocumentDb-API testen.

17
Ernesto

Der Schlüssel dazu ist, dass das CreateDocumentQuery, das Sie aufrufen, obwohl es als IOrderedQueryable zurückgegeben wird, das gekapselte Ergebnis auch von IDocumentQuery abgeleitet wird, was die Funktion .AsDocumentQuery() arbeiten.

Normalerweise solltest du dich nicht über das lustig machen, was du nicht besitzt. Wenn Sie jedoch ExecuteQueryAsync bis zum Abschluss üben möchten, können Sie eine gefälschte Abstraktion erstellen, mit der der Test bis zum Abschluss ausgeführt werden kann.

Das folgende Beispiel zeigt, wie es gemacht werden kann.

[TestClass]
public class DocumentDBRepositoryShould {
    /// <summary>
    /// Fake IOrderedQueryable IDocumentQuery for mocking purposes
    /// </summary>        
    public interface IFakeDocumentQuery<T> : IDocumentQuery<T>, IOrderedQueryable<T> {

    }

    [TestMethod]
    public async Task ExecuteQueryAsync() {
        //Arrange
        var description = "BBB";
        var expected = new List<MyDocumentClass> {
            new MyDocumentClass{ Description = description },
            new MyDocumentClass{ Description = "ZZZ" },
            new MyDocumentClass{ Description = "AAA" },
            new MyDocumentClass{ Description = "CCC" },

        };
        var response = new FeedResponse<MyDocumentClass>(expected);

        var mockDocumentQuery = new Mock<IFakeDocumentQuery<MyDocumentClass>>();
        mockDocumentQuery
            .SetupSequence(_ => _.HasMoreResults)
            .Returns(true)
            .Returns(false);

        mockDocumentQuery
            .Setup(_ => _.ExecuteNextAsync<MyDocumentClass>(It.IsAny<CancellationToken>()))
            .ReturnsAsync(response);

        var client = new Mock<IDocumentClient>();

        client
            .Setup(_ => _.CreateDocumentQuery<MyDocumentClass>(It.IsAny<Uri>(), It.IsAny<FeedOptions>()))
            .Returns(mockDocumentQuery.Object);

        var cosmosDatabase = string.Empty;

        var documentsRepository = new DocumentDBRepository<MyDocumentClass>(cosmosDatabase, client.Object);

        //Act
        var query = documentsRepository.GetQueryable(); //Simple query.

        var actual = await documentsRepository.ExecuteQueryAsync(query);

        //Assert
        actual.Should().BeEquivalentTo(expected);
    }
}
16
Nkosi