How to mock Amazon S3 Client for a .NET unit test

11,990

Solution 1

Instead of tightly coupling the code to implementation concerns, take note that AmazonS3Client class implements IAmazonS3 (v2,v3) or AmazonS3 (v1) interface according to referenced AWS SDK for .NET Documentation.

public class AmazonS3Client : IAmazonS3, IDisposable {
    //...
}

I would suggest you have your code depend on the abstraction (interface) and not on the implementation (class).

public class MyClass {
    private readonly IAmazonS3 client;

    public MyClass(IAmazonS3 client) {
        this.client = client;
    }

    //...
}

That would then allow greater flexibility when swapping dependencies with mocks when testing in isolation.

You have already experienced that added dependencies needed to mock the implementation of AmazonS3Client which can be avoided if the interface is used as the dependency.

s3ClientMock = new Mock<IAmazonS3>();

The Setup of the mocked interface can be done as you would normally as already demonstrated in your answer.

In production the properly configured implementation can be injected into dependent classes.

Solution 2

Using Moq, adding these constructor args seemed to work okay:

s3ClientMock= new Mock<AmazonS3Client>(
    FallbackCredentialsFactory.GetCredentials(true), 
    new AmazonS3Config{RegionEndpoint = RegionEndpoint.EUWest1});

And I can then set up as normal:

Stream GivenDocInS3Bucket(string pathToTestDoc)
{
  var docStream = new FileInfo(pathToTestDoc).OpenRead();

  s3ClientMock
    .Setup(x => x.GetObjectAsync(
       It.IsAny<string>(), 
       It.IsAny<string>(), 
       It.IsAny<CancellationToken>()))
    .ReturnsAsync(
       (string bucket, string key, CancellationToken ct) =>
         new GetObjectResponse
         {
           BucketName = bucket,
           Key = key,
           HttpStatusCode = HttpStatusCode.OK,
           ResponseStream = docStream,
         });

  return docStream;
}

But as Nkosi points out, I didn't need the constructor; I had simply overlooked the existence of the IAmazonS3 interface.

Share:
11,990
Chris F Carroll
Author by

Chris F Carroll

For testing snippets of C# code in SO questions, https://www.linqpad.net is the best thing since sliced bread. Do try it. CV: https://uk.linkedin.com/in/chrisfcarroll Blog: https://cafe-encounter.net

Updated on July 21, 2022

Comments

  • Chris F Carroll
    Chris F Carroll almost 2 years

    I note that the important members of AmazonS3Client are virtual, so a Mock<AmazonS3Client> should be easy. But

    new Mock<AmazonS3Client>()
    

    using Moq, got me an error saying that there was no valid RegionEndpoint.

    So obviously a little more is required.

  • Fabio
    Fabio about 6 years
    Or even better - introduce own interface which will wrap required functionality to methods you actually need. Which protect main code base from possible changes Amazon interface will have in the future.
  • Chris F Carroll
    Chris F Carroll about 6 years
    So it does. Now I'm embarrassed I missed it and am torn between accepting your answer and just deleting the question.
  • Chris F Carroll
    Chris F Carroll about 6 years
    @Fabio -- why do you think that's better? More code is worse.
  • Nkosi
    Nkosi about 6 years
    @ChrisFCarroll it is a valid point. It allows your API to be less fragile to changes made by Amazon to their SDK. As demonstrated by the update you had to make to my answer to take the new SDK versions into consideration.
  • Chris F Carroll
    Chris F Carroll about 6 years
    In most codebases, I think not, because if the SDK changes, your adapter code will likely change in step. My experience of people writing adapters is that it rarely improves a decent API (which someone at least thought about long enough to publish) ; and they often adapt towards specific requirements at the time they adapt. Which sometimes – not always – results in adapted adapted code when requirements evolve. So personally I would take the rule of thumb "less is better" before going with "the API might be fragile"
  • Nkosi
    Nkosi about 6 years
    @ChrisFCarroll you point is also very valid. I would say do what works best for you.