テストの実行と停止に合わせてミドルウェア用のコンテナを実行する

趣旨

aws/aws-sdk-go-v2@v0.18.0 で実装したコードのテストを書きたい。

実装例

こんな感じになった。

  • 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")
}