Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing for uploading to S3

I'm having trouble writing unit tests for a method that overwrites a file to a S3 bucket. The method grabs the original metadata of the file, and then overwrites the file with a new modified version and the same original metadata.

What I want the test to do is verify the inner methods like getObjectMetadata and putObject are called correctly with the right parameters

Here is the method:

 public void upload(File file, String account, String bucketName) {
        String key = "fakekey";
        ObjectMetadata objMData = client.getObjectMetadata(bucketName, key).clone();
        try {
            // cloning metadata so that overwritten file has same metadata as original file
            client.putObject(new PutObjectRequest(bucketName, key, file).withMetadata(objMData));
        } catch(AmazonClientException e) {
            e.printStackTrace();
        } 

Here is my test method:

@Mock
private AmazonS3 client = new AmazonS3Client();

public void testUpload() {

    S3Uploader uploader = new S3Uploader(client);

    File testFile = new File("file.txt");
    String filename = "file.txt";
    String bucketname = "buckettest";
    String account = "account";

    String key = account+filename;
    ObjectMetadata objMetadata = Mockito.mock(ObjectMetadata.class);
    when(client.getObjectMetadata(bucketname, key).clone()).thenReturn(objectMetadata);

    // can I make this line do nothing? doNothing()??
    doNothing.when(client.putObject(Matchers.eq(new PutObjectRequest(bucketName, key, file).withMetadata(objMData))));

    uploader.upload(aFile, anAccount, bucketName);

    // how do I verify that methods were called correctly??
    // what can I assert here?

}

I'm getting a NullPointerException at the line in my test

when(client.getObjectMetadata(bucketname, key).clone()).thenReturn(objectMetadata);

I'm not even able to reach the method call. Honestly, what I'm pretty much asking is, how do I verify that this upload() method is correct?

like image 577
jon givony Avatar asked Jan 30 '23 22:01

jon givony


2 Answers

The method you showed in your question uses a client instance to talk to S3. The client instance in the class to which this method belongs is either injected (at construction time, for example) or created (via a factory, perhaps). Assuming it is injected when the containing class is created then your test case might look like this:

@Test
public void testSomething() {
    AmazonS3 client = Mockito.mock(AmazonS3.class);

    S3Uploader uploader = new S3Uploader(client);

    String bucketName = "aBucketName";

    // ensures that the getObjectMetadata call fails thereby throwing the exception which your method catches
    Mockito.when(client.getObjectMetadata(Matchers.eq(bucketName), Matchers.eq("fakekey")).thenThrow(new AmazonServiceException());

    uploader.uploadToS3(aFile, anAccount, bucketName);

    // at this stage you would typically assert that the response 
    // from the upload invocation is valid but as things stand 
    // upload() swallows the exception so there's nothing to assert against
}


@Test
public void testSomethingElse() {
    AmazonS3 client = Mockito.mock(AmazonS3.class);

    S3Uploader uploader = new S3Uploader(client);

    String bucketName = "aBucketName";
    String key = "fakekey";
    File aFile = ...;
    ObjectMetadata objMData = ...;

    // ensures that the getObjectMetadata call succeeds thereby allowing the call to continue to the subsequent putObject invocation
    Mockito.when(client.getObjectMetadata(eq(bucketName), eq(key)).thenReturn(objMData);

    // ensures that the putObject call fails thereby throwing the exception which your method catches
    Mockito.when(client.putObject(Matchers.eq(new PutObjectRequest(bucketName, key, file).withMetadata(objMData)).thenThrow(new AmazonServiceException());

    uploader.uploadToS3(aFile, anAccount, bucketName);

    // at this stage you would typically assert that the response 
    // from the upload invocation is valid but as things stand 
    // upload() swallows the exception so there's nothing to assert against
}

The above code uses Mockito to mock the AmazonS3 client, this allows you to tweak the behaviour of your client instance such that your test invocations go down the 'throw exception' paths.

On a side note the catch clauses look a little odd since AmazonS3.putObject and AmazonS3.getObjectMetadata are both declared to throw AmazonServiceException and AmazonServiceException extends AmazonClientException.

like image 115
glytching Avatar answered Feb 02 '23 10:02

glytching


I would suggest you to use this project https://github.com/findify/s3mock. Create a mock of S3 bucket, and then you can test what happens when the bucket you look for exist or not.

like image 31
ronhash Avatar answered Feb 02 '23 12:02

ronhash