How can I unit test the getObject method of the AWS S3 SDK using Java?
I'm working with Java and I'm using the AWS SDK for interact with S3. I've the following method and I want to unit test it
private final S3Client s3Client;
...
...
public byte[] download(String key) throws IOException {
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket("myBucket")
.key(key)
.build();
return s3Client.getObject(getObjectRequest).readAllBytes();
}
For this purpose I'm using JUnit 5 and Mockito. The problem is that I don't know how to mock the result of
s3Client.getObject(getObjectRequest)
because the return type
ResponseInputStream<GetObjectResponse>
is a final class.
Any idea or suggestions? Thank you
Solution 1:
In case anyone is still looking for a different solution this is how I did it:
This is the code that needs to be mocked:
InputStream objectStream =
this.s3Client.getObject(
GetObjectRequest.builder().bucket(bucket).key(key).build(),
ResponseTransformer.toInputStream());
This is how to mock it:
S3Client s3Client = Mockito.mock(S3Client.class);
String bucket = "bucket";
String key = "key";
InputStream objectStream = getFakeInputStream();
when(s3Client.getObject(
Mockito.any(GetObjectRequest.class),
ArgumentMatchers
.<ResponseTransformer<GetObjectResponse, ResponseInputStream<GetObjectResponse>>>
any()))
.then(
invocation -> {
GetObjectRequest getObjectRequest = invocation.getArgument(0);
assertEquals(bucket, getObjectRequest.bucket());
assertEquals(key, getObjectRequest.key());
return new ResponseInputStream<>(
GetObjectResponse.builder().build(), AbortableInputStream.create(objectStream));
});
Solution 2:
The problem is solved. In a maven project you can add a file named "org.mockito.plugins.MockMaker" in the folder "src/test/resources/mockito-extensions".
Inside the file, add "mock-maker-inline" without quotes.
From now Mockito will be able to mock final classes also.
Solution 3:
I was able to get this going in Spock with a GroovyMock that uses Objenesis. I know it's not the original poster's stack but this came up in my search so I thought I'd respond here in case parts of this help anyone else.
S3Repo to Test
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.GetObjectRequest
class S3Repo {
S3Client s3
String bucket
def getS3ObjectText(String key) {
def response
try {
response = s3.getObject(GetObjectRequest
.builder().bucket(bucket).key(key).build() as GetObjectRequest)
return response.text
} finally {
if (response) response.close()
}
}
}
Spock Test
import software.amazon.awssdk.core.ResponseInputStream
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import spock.lang.Specification
class S3RepoTest extends Specification {
def "get object text"() {
given:
def response = GroovyMock(ResponseInputStream)
def s3Text = 'this would be in file stored in s3'
def key = 'my-file.txt'
def s3 = Mock(S3Client)
def bucket = 'mock-bucket'
def repo = new S3Repo(s3: s3, bucket: bucket)
when:
def text = repo.getS3ObjectText(key)
then:
1 * s3.getObject(_) >> { args ->
def req = args.first() as GetObjectRequest
assert req.bucket() == bucket
assert req.key() == key
return response
}
1 * response.text >> s3Text
and:
text == s3Text
}
}
I think the critical piece here is the GroovyMock
which requires Objenesis. You can certainly test your Java code with Groovy and you could probably use the GroovyMock
in JUnit.