テストの実行と停止に合わせてミドルウェア用のコンテナを実行する
趣旨
aws/aws-sdk-go-v2@v0.18.0 で実装したコードのテストを書きたい。
- 課題
- Mocking out new API? · Issue #70 · aws/aws-sdk-go-v2 · GitHub という理由でモックするのが面倒
- 対処
- 👍 testcontainers-go を利用する
- 実行するコンテナは localstack/localstack
- 👎 本物を使用する
- 👍 testcontainers-go を利用する
- 課題
- JUnit の
@BeforeClass/@AfterClass
に相当する機能が欲しい
- JUnit の
- 対処
- 👍 testify の
Suite
を利用する - 👎
testing.Main
でsetup/teardown
を実装する
- 👍 testify の
実装例
こんな感じになった。
- Good
- 下の方だけ見ればいいのでわりと見通しがいいと思う
- 実行時間は 30 秒前後
localstack
の開始が遅いだけでコードは悪くなかった
- Bad
- 冗長さを感じる 😔
suite.Assert()
はAssume
みたい- 検証に失敗しても止まらない
suite.Require()
はAssert
みたい- 検証に失敗すると止まる
package iam_test import ( "context" "fmt" "testing" "time" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/suite" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/external" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/iam/iamiface" testcontainers "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) type testResourceDTO struct { ctx context.Context container testcontainers.Container awsConfig aws.Config clientAPI iamiface.ClientAPI } var testResource testResourceDTO // for awssdk var ( awsRegion = "ap-northeast-1" ) // for localstack var ( imageName = "localstack/localstack" imageTag = "0.10.7" containerPort = "4593" // IAM see https://github.com/localstack/localstack ) type TestSuite struct { suite.Suite } func TestSuiteTest(t *testing.T) { suite.Run(t, new(TestSuite)) } // @BeforeClass func (suite *TestSuite) SetupSuite() { testResource.ctx = context.Background() container, err := testcontainers.GenericContainer(testResource.ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ Image: fmt.Sprintf("%s:%s", imageName, imageTag), ExposedPorts: []string{fmt.Sprintf("%s/tcp", containerPort)}, // XXX ForListeningPort がうまく働かない WaitingFor: wait.ForLog("Ready.").WithStartupTimeout(60 * time.Second), Env: map[string]string{ "SERVICES": "iam", "IAM_PORT": containerPort, "USE_SSL": "false", "START_WEB": "0", }, }, Started: true, }) suite.Require().Nil(err, "testcontainers.GenericContainer") testResource.container = container config, err := external.LoadDefaultAWSConfig(external.WithRegion(awsRegion)) suite.Require().Nil(err, "external.LoadDefaultAWSConfig") serviceHost, err := container.Host(testResource.ctx) suite.Require().Nil(err, "contaienr.Host") servicePort, err := container.MappedPort(testResource.ctx, nat.Port(containerPort)) suite.Require().Nil(err, "contaienr.MappedPort") url := fmt.Sprintf("http://%s:%s", serviceHost, servicePort.Port()) // エンドポイントを testcontainer の localstack に差し替える config.EndpointResolver = aws.ResolveWithEndpointURL(url) testResource.awsConfig = config } // @AfterClass func (suite *TestSuite) TearDownSuite() { suite.Require().NotNil(testResource.container, "testContainer") suite.Require().Nil(testResource.container.Terminate(testResource.ctx), "testContainer.Terminate") } // @Before func (suite *TestSuite) SetupTest() { testResource.clientAPI = iam.New(&testResource.awsConfig) } // ここからテストケース func (suite *TestSuite) TestCreateRole() { name := "test" path := "/path/" description := "description" assumeRolePolicyDocument := "{json string}" input := &iam.CreateRoleInput{ RoleName: aws.String(name), Path: aws.String(path), Description: aws.String(description), AssumeRolePolicyDocument: aws.String(assumeRolePolicyDocument), } suite.Assert().Nil(input.Validate(), "validation error") req := c.CreateRoleRequest(input) res, err := req.Send(context.Background()) suite.Assert().Nil(err, "send error") suite.Assert().Equal(name, res.Role.RoleName, "name") suite.Assert().NotEmpty(res.Role.Arn, "arn") }