Small project demonstrating how I test interaction with DynamoDB locally
Launch dynamoDB by running
docker run -d -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
This will run DynamoDB API on port 8080. Test the API by running
# just append `--endpoint-url http://localhost:8000` to any aws-cli command to test
aws dynamodb list-tables --endpoint-url http://localhost:8000
To point a DynamoDB client to local DB, use the following snippets
customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: "http://localhost:8000", // IMPORTANT
}, nil
})
awsCfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithEndpointResolver(customResolver),
)
if err != nil {
return nil, nil, err
}
svc := dynamodb.NewFromConfig(awsCfg)
To set up dynamoDB locally, just call CreateTable
method on dynamodb.Client.CreateTable()
as you normally would.
By instantiating dynamodb.Client
with aws.Endpoint.URL = "http://localhost:8000"
, that tells the dynamoDB client
to call an API at that endpoint instead of the real one.
Once you have a locally pointed dynamoDB client, you can simply call client.CreateTable
to create a table.
Use the following snippets by passing in the local client as dynamodb.Client
. Note that this
method returns a function that's used to clean up after itself. See example below on how to use the cleanup func.
// testhelper/db.go
func SetupTable(t *testing.T, tableName string, dynamoClient *dynamodb.Client) func(){
t.Helper()
createInput := &dynamodb.CreateTableInput{
AttributeDefinitions: []types.AttributeDefinition{
{
AttributeName: aws.String("pk"),
AttributeType: types.ScalarAttributeTypeS,
},
{
AttributeName: aws.String("sk"),
AttributeType: types.ScalarAttributeTypeS,
},
},
KeySchema: []types.KeySchemaElement{
{
AttributeName: aws.String("pk"),
KeyType: types.KeyTypeHash,
},
{
AttributeName: aws.String("sk"),
KeyType: types.KeyTypeRange,
},
},
BillingMode: types.BillingModePayPerRequest,
TableName: aws.String(tableName),
}
_, err := dynamoClient.CreateTable(context.TODO(), createInput)
if err != nil {
t.Fatal(err)
}
// now wait until it's ACTIVE
start := time.Now()
time.Sleep(time.Second)
describeInput := &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
}
for {
resp, err := dynamoClient.DescribeTable(context.TODO(), describeInput)
if err != nil {
t.Fatal(err)
}
if resp.Table.TableStatus == types.TableStatusActive {
break
}
if time.Since(start) > time.Minute {
t.Fatalf("timed out creating DynamoDB table %s", tableName)
}
time.Sleep(3 * time.Second)
}
return func() {
params := &dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
}
_, err := dynamoClient.DeleteTable(context.TODO(), params)
if err != nil {
t.Logf("WARNING: Failed to delete the test DynamoDB table %q", err)
}
}
}
Example test:
t.Run("testing", func(t *testing.T) {
// setup custom resolver
svc := dynamodb.NewFromConfig(awsCfg)
cleanup := SetupTable(t, "table", svc)
t.Cleanup(cleanup) // or defer cleanup()
// Do the actual test
}
AWS credentials are still required eventhough no real AWS API being called. That means if you're running your test in a CI system, you need to set credentials on the runner. These credentials DO NOT have to be valid though.
Example:
# Skip if you had AWS Credentials already
export AWS_ACCESS_KEY_ID=fakeCred
export AWS_SECRET_ACCESS_KEY=fakeCred
export AWS_DEFAULT_REGION=us-west-2
# --------------------------------------
docker run --name dynamo-local -d -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
go test ./... -count=1
docker stop dynamo-local