How to mock Amazon S3 Client for a .NET unit test
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.
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, 2022Comments
-
Chris F Carroll almost 2 years
I note that the important members of
AmazonS3Client
are virtual, so aMock<AmazonS3Client>
should be easy. Butnew Mock<AmazonS3Client>()
using Moq, got me an error saying that there was no valid
RegionEndpoint
.So obviously a little more is required.
-
Fabio about 6 yearsOr 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 about 6 yearsSo it does. Now I'm embarrassed I missed it and am torn between accepting your answer and just deleting the question.
-
Chris F Carroll about 6 years@Fabio -- why do you think that's better? More code is worse.
-
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 about 6 yearsIn 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 about 6 years@ChrisFCarroll you point is also very valid. I would say do what works best for you.