Ложный CloudBlobClient с AutoFac и AutoMock

3

Я пытаюсь написать модульные тесты для моего AzureBlobRepository. Репозиторий получает CloubBlobClient в конструкторе. Я хочу высмеять клиента, но это дает исключение:

using (var mock = AutoMock.GetLoose())
{
    var mockClient = mock.Mock<CloudBlobClient>();
}

Невозможно выбрать между несколькими конструкторами с равной длиной 2 по типу "Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient". Когда конструктор будет зарегистрирован, выберите конструктор явно с помощью метода конфигурации UsingConstructor().

Конечно, в моем модульном тесте я ничего не регистрирую, так что сообщение не очень полезно.

Я также пробовал другие способы, как предоставление NameParameters, TypedParameters или вызов mock.Create вместо mock.Mock, но все, что я пытаюсь, возвращает одно и то же сообщение об исключении.

(такая же проблема возникает и для CloudBlobContainer)

UPDATE после реализации интерфейсов здесь является примером модульного теста, который я написал:

[TestMethod]
public void AzureBlobRepository_GetByIdAsync_ReturnsContent()
{    
    Guid blobId = Guid.NewGuid();
    Guid directoryName = Guid.NewGuid();
    string containerName = "unittest";

    using (var mock = AutoMock.GetLoose())
    {
        var mockClient = mock.Mock<ICloudBlobClient>();
        var mockContainer = mock.Mock<ICloudBlobContainer>();
        var mockDirectory = mock.Mock<ICloudBlobDirectory>();
        // notice that we're not using AutoMock here, it fails to create the mock
        var mockBlob = new Mock<CloudBlockBlob>(new Uri($"http://tempuri.org/{containerName}/{directoryName}/{blobId}"));
        mockBlob.Setup(m => m.DownloadTextAsync()).Returns(Task.FromResult("content"));

        mockClient.Setup(m => m.GetContainerReference(containerName))
            .Returns(mockContainer.Object);
        mockContainer.Setup(m => m.GetDirectoryReference(directoryName.ToString()))
            .Returns(mockDirectory.Object);
        mockDirectory.Setup(m => m.GetBlockBlobReference(blobId.ToString()))
            .Returns(mockBlob.Object);

        var repository = mock.Create<AzureBlobRepository>(
            new TypedParameter(typeof(ICloudBlobClient), mockClient.Object),
            new NamedParameter("container", containerName),
            new NamedParameter("directory", directoryName));

        var result = repository.GetByIdAsync(blobId, directoryName).Result;
        result.ShouldBe("content");
    }
}
  • 0
    Эти классы должны рассматриваться как сторонние проблемы реализации. Это означает, что вы не можете их контролировать, и нам не следует издеваться над тем, что мы не можем контролировать. Они должны быть заключены в абстракции, которыми вы управляете, и можете при необходимости имитировать их при тестировании в изоляции. Классы должны зависеть от абстракции, а не от конкреций по этой самой причине. Насмешливые конкретные классы имеют тенденцию стучать в эффекты
  • 0
    Эти классы были изменены (методы стали виртуальными) в последних обновлениях SDK хранилища Azure для модульного тестирования.
Показать ещё 4 комментария
Теги:
unit-testing
azure-storage-blobs
autofac
moq

3 ответа

2
Лучший ответ

Эти классы следует рассматривать как проблемы сторонних разработчиков. Это означает, что вы не контролируете их, и мы не должны издеваться над тем, что мы не контролируем. Они должны быть инкапсулированы за абстракциями, которые вы контролируете, и можете издеваться по мере необходимости при тестировании в изоляции.

public interface ICloubBlobClient {
    //...expose only the functionality I need
}

public class CloubBlobClientWrapper : ICloubBlobClient {
    private readonly CloubBlobClient client;

    public CloubBlobClientWrapper(CloubBlobClient client) {
        this.client = client;
    }

    //...implement interface wrapping
}

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

Обертке не нужно точно обертывать клиента, но может агрегировать функциональные возможности, чтобы не подвергать проблемы реализации.

Итак, теперь, тестируя в изоляции, вы можете высмеивать абстракцию, которую вы контролируете

using (var mock = AutoMock.GetLoose()) {
    var mockClient = mock.Mock<ICloudBlobClient>();

    /// ...and the rest of the test.
}
  • 1
    Да, этот подход работает. Я закончил с интерфейсами для CloudBlobClient, CloudBlobContainer и CloudBlobDirectory и обертками для этих интерфейсов. Так что довольно много дополнительной работы, но, по крайней мере, я могу продолжить писать модульные тесты сейчас! Я обновил оригинальный вопрос с помощью тестового модуля
0

Мне удалось NSubstitute это с помощью NSubstitute, я только издевался над функциями, которые я использую.

/// <summary>
/// Create a mock for CloudBlobClient
/// </summary>
/// <param name="containerExists"></param>
/// <returns></returns>
private CloudBlobClient GetMock(bool containerExists = true)
{
    var items = new List<IListBlobItem>();
    var client = Substitute.For<CloudBlobClient>(new Uri("http://foo.bar/"), null);
    var container = Substitute.For<CloudBlobContainer>(new Uri("http://foo.bar/"));
    client.GetContainerReference(Arg.Any<string>()).Returns(container);
    container.ExistsAsync(Arg.Any<CancellationToken>()).Returns(Task.FromResult(containerExists));
    container.ListBlobsSegmentedAsync(Arg.Any<string>(), Arg.Any<bool>(), 
                                        Arg.Any<BlobListingDetails>(), 
                                        Arg.Any<int?>(), 
                                        Arg.Any<BlobContinuationToken>(), 
                                        Arg.Any<BlobRequestOptions>(), 
                                        Arg.Any<OperationContext>(), 
                                        Arg.Any<CancellationToken>())
                                        .Returns(ci => new BlobResultSegment(items.ToArray(), null));

    container.GetBlockBlobReference(Arg.Any<string>()).Returns(ci => GetBlockBlobMock(ci.ArgAt<string>(0), items));
    return client;
}

/// <summary>
/// Create a mock for CloudBlockBlob
/// </summary>
/// <param name="name"></param>
/// <param name="listBlobItems"></param>
/// <returns></returns>
private CloudBlockBlob GetBlockBlobMock(string name, List<IListBlobItem> listBlobItems)
{
    var created = DateTimeOffset.Now;
    var bufferStream = new MemoryStream();
    var blob = Substitute.For<CloudBlockBlob>(new Uri("https://foo.blob.core.windows.net/bar/" + name + ".txt"));
    //We can't mock the value the normal way, use reflection to change its value!
    blob.Properties.GetType().GetProperty(nameof(blob.Properties.Created)).SetValue(blob.Properties, created);
    //we cant mock properties! (Dam this wont work)
    blob.UploadFromStreamAsync(Arg.Any<Stream>(),
                                Arg.Any<AccessCondition>(),
                                Arg.Any<BlobRequestOptions>(),
                                Arg.Any<OperationContext>(),
                                Arg.Any<CancellationToken>()).Returns(ci => {
                                    var stream = ci.Arg<Stream>();
                                    stream.CopyTo(bufferStream);
                                    listBlobItems.Add(blob);
                                    return Task.CompletedTask;
                                });

    blob.DownloadToStreamAsync(Arg.Any<Stream>(),
                                Arg.Any<AccessCondition>(),
                                Arg.Any<BlobRequestOptions>(),
                                Arg.Any<OperationContext>(),
                                Arg.Any<CancellationToken>()).Returns(ci =>
                                {
                                    var stream = ci.Arg<Stream>();
                                    bufferStream.Position = 0;
                                    bufferStream.CopyTo(stream);
                                    stream.Position = 0;
                                    return Task.CompletedTask;
                                });
    return blob;
}

Я не уверен на 100%, что он на 100% точен, но он позволяет мне запускать некоторые юнит-тесты!

0

См. Этот CloudBlobContainer. Этот тип содержит три конструктора. Для создания экземпляра типа требуется конструктор. Попробуйте ввести свой код в new CloudBlobContainer и вам нужно будет выбрать один из трех конструкторов. AutoMock не может знать, какой конструктор должен выбрать.

Вы можете сказать AutoMock, как создать CloudBlobContainer

Образец:

using (var mock = AutoMock.GetLoose())
{
    mock.Provide<CloudBlobContainer, CloudBlobContainer>(new NamedParameter("uri", new Uri("your uri")));
    var mockClient = mock.Mock<CloudBlobClient>();
}
  • 0
    Жаль, издевательство. Предоставить точно такое же исключение
  • 0
    Да, это неявно и сложно, чем я ожидал. Это похоже на дефект в AutoMock. Нужно читать источники AutoMock
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню