web/Spring

S3Mock을 사용한 S3 테스트 방법

반응형

s3를 로컬에서 테스트 진행하기 위해서는 별도의 mock 서버가 필요하다. 이를 대신해서 AWS s3에서 사용하는 api 구현체를 제공해주는 s3Mock이라는 라이브러리가 있어 사용해봤는데 괜찮아서 정리해본다.

 

Gradle Import

https://github.com/findify/s3mock

 

GitHub - findify/s3mock: Embedded S3 server for easy mocking

Embedded S3 server for easy mocking. Contribute to findify/s3mock development by creating an account on GitHub.

github.com

dependencies{
	api 'org.springframework.cloud:spring-cloud-starter-aws:2.1.3.RELEASE'
    testImplementation 'io.findify:s3mock_2.12:0.2.4'
}

test scope로 s3mock 라이브러리를 추가한다. 0.2.6버전이 현재기준으로 최신버전이지만 실제 사용하는 부분에서 버그가 있어서 그 아래버전인 0.2.4를 사용했다. 버그 내용은 동작 코드를 적으면서 자세히 기록하겠다.

 

 

Configuration

@Profile("test")
@TestConfiguration
public class S3MockConfig {

    @Bean(name = "s3Mock")
    public S3Mock s3Mock() {
        return new S3Mock.Builder().withPort(8001).withInMemoryBackend().build();
    }

    @Primary
    @Bean(name = "amazonS3", destroyMethod = "shutdown")
    public AmazonS3 amazonS3(){
        AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration("http://127.0.0.1:8001", Regions.AP_NORTHEAST_2.name());
        AmazonS3 client = AmazonS3ClientBuilder
            .standard()
            .withPathStyleAccessEnabled(true)
            .withEndpointConfiguration(endpoint)
            .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()))
            .build();
        return client;
    }

}

우선 S3Mock 서버로 사용될 S3Mock을 bean으로 등록해야한다. 서버로 사용될 port를 등록하고 서버에 파일을 등록할지 file로 저장할지에 대해서 withInMemoryBackend, withFileBackend를 사용해서 지정할 수 있으나 실제 저장유무를 보는 테스트이기 때문에 메모리에서만 확인해도 충분해서 withInMemoryBackend로 진행한다.

 

그리고 amazonS3 client를 생성하여 s3에 요청을 보낼 client를 bean으로 등록한다. 그리고 현재 이 aws client는 aws ec2 server에서 동작하는것이 아니기 때문에 region auto설정을 false로 지정해줘야한다.

cloud:
  aws:
    region:
      static: ap-northeast-2
      auto: false
    stack:
      auto: false

 

 

Test Code

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import io.findify.s3mock.S3Mock;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.util.FileCopyUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.assertThat;

@Import(S3MockConfig.class)
@ActiveProfiles("test")
@SpringBootTest
public class S3Test {

    @Autowired
    private AmazonS3 amazonS3;

    @Autowired
    private static final String BUCKET_NAME = "wedul";

    @BeforeAll
    static void setUp(@Autowired S3Mock s3Mock, @Autowired AmazonS3 amazonS3) {
        s3Mock.start();
        amazonS3.createBucket(BUCKET_NAME);
    }

    @AfterAll
    static void tearDown(@Autowired S3Mock s3Mock, @Autowired AmazonS3 amazonS3) {
        amazonS3.shutdown();
        s3Mock.stop();
    }

    @Test
    @DisplayName("s3 import 테스트")
    void S3Import() throws IOException {
        // given
        String path = "test/02.txt";
        String contentType = "text/plain";
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, path, new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8)), objectMetadata);
        amazonS3.putObject(putObjectRequest);

        // when
        S3Object s3Object = amazonS3.getObject(BUCKET_NAME, path);

        // then
        assertThat(s3Object.getObjectMetadata().getContentType()).isEqualTo(contentType);
        assertThat(new String(FileCopyUtils.copyToByteArray(s3Object.getObjectContent()))).isEqualTo("");
    }

}

BeforeAll

BeforeAll에서 S3Mock서버를 실행시켜야하고 test에서 사용할 Bucket을 미리 생성해둔다.

 

AfterAll

AfterAll에서 테스트가 종료된 후 s3Mock을 종료시켜줘야하는데 여기서 0.2.6버전에서는 서버가 종료되지 않는 문제가 발생하는데 stop, shutdown어떤 메소드를 사용해도 서버가 종료되지 않는다. 서버가 종료되지 않을 경우 springboottest를 위해 올라갔던 application이 종료되지 못해서 테스트가 끝나지 못하는 이슈가 발생된다. 다행히도 0.2.4버전에서는 정상적으로 종료가 되어 사용이 가능했다.

 

Test

테스트는 s3에 bucket, path, content를 집어넣고 다시 꺼냈을 때 정상적으로 기록되었는지 확인하는 상태 테스트를 진행했다.

 

 

 

s3를 로컬에서 테스트하거나 로컬에서 S3가 포함된 서버를 실행시키고 싶을때도 사용할 수 있을 것 같다. 이 외에도 adobe/s3Mock (https://github.com/adobe/S3Mock)도 있는데 이건 사용해보지 않았지만 우선은 findify/s3mock만 사용해도 충분할 것 같다.

반응형