I have an addition to all the great answers here.
There is an opinion that developers should not mock the types and APIs they don't own. Here are a few links on this topic:
TLDR: by mocking you introduce some assumptions of how things should work into your tests. So your tests are not really black-box anymore, while they should be. Your assumptions may be wrong. Or the API you've mocked could change, making your mocks and tests drift and stale. This could result in a broken code passing the tests.
Need an example? Consider this code:
@Test
void test() throws Exception {
GetObjectRequest request = GetObjectRequest
.builder()
.bucket("bucket")
.key("file.json")
.range("bytes=0-0,-1")
.build();
ResponseInputStream<GetObjectResponse> response = client.getObject(request);
byte[] bytes = response.readAllBytes();
Assertions.assertEquals("{}", new String(bytes));
}
One would expect it to successfully read first and last bytes of a JSON file. Which would give {}
(assuming there are no extra whitespaces or newlines). So she mocks the getObject
and returns those bytes.
What happens in the reality? She'll get the whole JSON, not just the first and last bytes, because
Amazon S3 doesn't support retrieving multiple ranges of data per GET request.
This is just how it works in the real world: no exceptions, no warnings, just the whole content being returned.
If you leave only one range, let's say only the first byte, it would work:
@Test
void test() throws Exception {
GetObjectRequest request = GetObjectRequest
.builder()
.bucket("bucket")
.key("file.json")
.range("bytes=0-0")
.build();
ResponseInputStream<GetObjectResponse> response = client.getObject(request);
byte[] bytes = response.readAllBytes();
Assertions.assertEquals("{", new String(bytes));
}
Although this example is completely made-up, and cases like this are rare, I hope you've got the idea.
Solution?
Integration tests! Yes, it's a higher level and writing them is more effort, but they make it possible to catch bugs like the one above.
So, next time think about writing an integration test and using a real thing instead of a mock. For some technologies you would even find an official lightweight implementations. Like DynamoDB Local. Or use a mock that is closer to the original API. Like a LocalStack.
And if you are a lucky user of Java/Kotlin and JUnit 5 I would like to recommend you to use aws-junit5
, a set of JUnit 5 extensions for AWS. I am it's author. These extensions can be used to inject clients for AWS services provided by tools like LocalStack or any other AWS-compatible API (including the real AWS, of course). Both AWS Java SDK v 2.x and v 1.x are supported. You aws-junit5
to inject clients for S3, DynamoDB, Kinesis, SES, SNS and SQS. Read more in the user guide.