Reputation: 1027
I'm trying to write unit tests for my AzureBlobRepository. The repository receives a CloubBlobClient in the constructor. I want to mock the client, but this gives an exception:
using (var mock = AutoMock.GetLoose())
{
var mockClient = mock.Mock<CloudBlobClient>();
}
Cannot choose between multiple constructors with equal length 2 on type 'Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient'. Select the constructor explicitly, with the UsingConstructor() configuration method, when the component is registered.
Ofcourse, in my unit test I am not registering anything, so that message is not very helpful.
I also tried other ways like providing NameParameters, TypedParameters, or calling mock.Create in stead of mock.Mock, but everything I try returns the same exception message.
(same problem also occurs on CloudBlobContainer)
UPDATE after implementing interfaces here is an example of a unit test that I wrote:
[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");
}
}
Upvotes: 4
Views: 7375
Reputation: 247088
Those classes should be treated as 3rd party implementation concerns. This means that you have no control over them and we should not mock what we have no control over. They should be encapsulated behind abstractions that you do control and can mock as needed when testing in isolation.
public interface ICloudBlobClient {
//...expose only the functionality I need
}
public class CloudBlobClientWrapper : ICloudBlobClient {
private readonly CloudBlobClient client;
public CloudBlobClientWrapper(CloudBlobClient client) {
this.client = client;
}
//...implement interface wrapping
}
Classes should depend on abstraction and not concretions for that very reason. Mocking concrete classes tend to have knock on effects
The wrapper does not need to wrap the client exactly but can aggregate functionality so as not to expose implementation concerns.
So now when testing in isolation you can mock the abstraction you control
using (var mock = AutoMock.GetLoose()) {
var mockClient = mock.Mock<ICloudBlobClient>();
/// ...and the rest of the test.
}
Upvotes: 6
Reputation: 38465
I have managed to mock it using NSubstitute
, i have only mocked the functions that i use.
/// <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;
}
I'm not 100% sure its 100% accurate but it allows me to run some unit tests!
Upvotes: 0
Reputation: 27
See this CloudBlobContainer. This type contains three constructors. And constructor is required for create instance of type. Try to type in your code new CloudBlobContainer
and you will need to choose one of three constructors. AutoMock cannot know what constructor must choose.
You can say to AutoMock how to create CloudBlobContainer
Sample:
using (var mock = AutoMock.GetLoose())
{
mock.Provide<CloudBlobContainer, CloudBlobContainer>(new NamedParameter("uri", new Uri("your uri")));
var mockClient = mock.Mock<CloudBlobClient>();
}
Upvotes: 0