Search code examples
amazon-s3spring-cloudlocalstacktestcontainers

Spring Cloud: testing S3 client with TestContainters


I use Spring Cloud's ResourceLoader to access S3, e.g.:

public class S3DownUpLoader {

private final ResourceLoader resourceLoader;

@Autowired
public S3DownUpLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
}
public String storeOnS3(String filename, byte[] data) throws IOException {
    String location = "s3://" + bucket + "/" + filename;
    WritableResource writeableResource = (WritableResource) this.resourceLoader.getResource(location);
    FileCopyUtils.copy( data, writeableResource.getOutputStream());
    return filename;
}

It works okey and I need help to test the code with Localstack/Testcontainers. I've tried following test, but it does not work - my production profile gets picked up(s3 client with localstack config is not injected):

@RunWith(SpringRunner.class)
@SpringBootTest
public class S3DownUpLoaderTest {

@ClassRule
public static LocalStackContainer localstack = new LocalStackContainer().withServices(S3);

@Autowired
S3DownUpLoader s3DownUpLoader;

@Test
public void testA() {
     s3DownUpLoader.storeOnS3(...);
}


@TestConfiguration
@EnableContextResourceLoader
public static class S3Configuration {

    @Primary
    @Bean(destroyMethod = "shutdown")
    public AmazonS3 amazonS3() {
        return AmazonS3ClientBuilder
                .standard()
                .withEndpointConfiguration(localstack.getEndpointConfiguration(S3))
                .withCredentials(localstack.getDefaultCredentialsProvider())
                .build();
    }

}
}

Solution

  • It seems the only way to provide custom amazonS3 bean for ResourceLoader is to inject it manually. The test looks like

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ContextConfiguration(classes = S3DownUpLoaderTest.S3Configuration.class)
    public class S3DownUpLoaderTest implements ApplicationContextAware {
    
    private static final String BUCKET_NAME = "bucket";
    
    @ClassRule
    public static LocalStackContainer localstack = new LocalStackContainer().withServices(S3);
    
    @Autowired
    S3DownUpLoader s3DownUpLoader;
    
    @Autowired
    SimpleStorageProtocolResolver resourceLoader;
    
    @Autowired
    AmazonS3 amazonS3;
    
    @Before
    public void setUp(){
        amazonS3.createBucket(BUCKET_NAME);
    }
    
    @Test
    public void someTestA() throws IOException {
        ....
    
    }
    
    @After
    public void tearDown(){
        ObjectListing object_listing = amazonS3.listObjects(QLM_BUCKET_NAME);
        while (true) {
            for (S3ObjectSummary summary : object_listing.getObjectSummaries()) {
                amazonS3.deleteObject(BUCKET_NAME, summary.getKey());
            }
    
            // more object_listing to retrieve?
            if (object_listing.isTruncated()) {
                object_listing = amazonS3.listNextBatchOfObjects(object_listing);
            } else {
                break;
            }
        };
    
        amazonS3.deleteBucket(BUCKET_NAME);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (applicationContext instanceof ConfigurableApplicationContext) {
            ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
            configurableApplicationContext.addProtocolResolver(this.resourceLoader);
        }
    
    }
    
    public static class S3Configuration {
    
        @Bean
        public S3DownUpLoader s3DownUpLoader(ResourceLoader resourceLoader){
            return new S3DownUpLoader(resourceLoader);
        }
    
        @Bean(destroyMethod = "shutdown")
        public AmazonS3 amazonS3() {
            return AmazonS3ClientBuilder
                    .standard()
                    .withEndpointConfiguration(localstack.getEndpointConfiguration(S3))
                    .withCredentials(localstack.getDefaultCredentialsProvider())
                    .build();
        }
    
        @Bean
        public SimpleStorageProtocolResolver resourceLoader(){
            return new SimpleStorageProtocolResolver(amazonS3());
        }
    
    }