Search code examples
javaunit-testingamazon-s3junitspring-cloud

How to write unit test upload directory to S3?


I have a method to upload directory to s3. I would like to write a unit test for it and I've been searching, but most test are integration tests. Maybe I'm not supposed to do a unit test for this kind of process? I don't have a privileges to use testcontainers for integration test, so I can’t use testcontainers.

Here is my code

public void uploadDir(final Path path, final String key) throws InternalServerError {
        final TransferManager transferManager = TransferManagerBuilder.standard()
                                                                      .withS3Client(s3Client)
                                                                      .build();
       

            final ProgressListener progressListener = progressEvent -> {
                if (progressEvent.getBytesTransferred() > 0) {
                    double percentTransferred = progressEvent.getBytesTransferred() * 100.0 / progressEvent.getBytes();
                    log.info("Transferred " + percentTransferred + "%");
                }
            };

            final MultipleFileUpload xfer = transferManager.uploadDirectory("mybucket", key, path.toFile(), true);
            xfer.addProgressListener(progressListener);
            xfer.waitForCompletion();
            
           log.info("Upload has been completed.");
        } catch (final AmazonServiceException | InterruptedException exception) {
            
            ... do something here
        } finally {
            transferManager.shutdownNow();
        }
    } 

Solution

  • It seems there's not much to unit test in the method but arguments and interactions.
    Mocking TransferManager isn't really a solution because you'd need to mock MultipleFileUpload, and also the code that calls ProgressListener needs to be added. You'd end up mocking everything which makes it useless. Testing a widely used library also has not much value for you.

    I'd test the exception case which is an unlikely case to happen (aka not repeatable) during integration testing.
    Roughly something like this (assuming mockito):

    @Test
    void uploadDirFailsTest() {
      TransferManager mockTM = mock(TransferManager.class);
    
      doThrow(new AmazonServiceException(...)) // hopefully it has a public constructor
           .when(mockTM)
           .uploadDirectory(eq("mybucket"), any(), any(), eq(true)); //more matching if needed
      
      YourClassName instance = new YourClassName(mockTM);
    
      instance.uploadDir(path, key);
      
    /*
    verify correct exception handling here or
    if you rethrow the exception wrap the call with
    assertThrows()
    */
    
    }
    
    
    
    public class YourClassName {
    final TransferManager transferManager;
    
    public YourClassName(TransferManager transferManager) {
      this.transferManager = transferManager;
    }
    
    public void uploadDir(final Path path, final String key) throws InternalServerError {
                final ProgressListener progressListener = progressEvent -> {
                    if (progressEvent.getBytesTransferred() > 0) {
                        double percentTransferred = progressEvent.getBytesTransferred() * 100.0 / progressEvent.getBytes();
                        log.info("Transferred " + percentTransferred + "%");
                    }
                };
    
                final MultipleFileUpload xfer = transferManager.uploadDirectory("mybucket", key, path.toFile(), true);
                xfer.addProgressListener(progressListener);
                xfer.waitForCompletion();
                
               log.info("Upload has been completed.");
            } catch (final AmazonServiceException | InterruptedException exception) {
                
                ... do something here
            } finally {
                transferManager.shutdownNow();
            }
        } 
    
    //real use
    new YourClassName(
        TransferManagerBuilder.standard() 
            .withS3Client(s3Client)
            .build()
    );