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.