George Ivanov
George Ivanov

Reputation: 31

LocalStackContainer integration testing a lambda

Is there a way we can (integration) test a Lambda for AWS using testcontainers & the following LocalStackContainer:

new LocalStackContainer(DockerImageName.parse('localstack/localstack:0.11.6'))
    .withServices(LocalStackContainer.Service.LAMBDA)

Specifically, how do I "upload" the Lambda to the locally running service, so that I can then trigger & test it?

Upvotes: 2

Views: 4915

Answers (2)

rieckpil
rieckpil

Reputation: 12021

Once the LocalStackContainer is started, you can execute commands inside the container to set up your infrastructure. LocalStack provides the awslocal binary to create any AWS resources.

Inside your test, you can then have a @BeforeAll step where you use your container reference and create everything.

The following example does this for S3 and SQS (reference from this blog post):

@Testcontainers
@SpringBootTest
public class YourLocalStackIT {
 
  @Container
  static LocalStackContainer localStack = new LocalStackContainer("0.11.6")
    .withServices(S3, SQS)
    .withEnv("DEFAULT_REGION", "eu-central-1");
 
  @BeforeAll
  static void beforeAll() throws IOException, InterruptedException {
    localStack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", QUEUE_NAME);
    localStack.execInContainer("awslocal", "s3", "mb", "s3://" + BUCKET_NAME);
  }
 
  private static final String QUEUE_NAME = "order-event-test-queue";
  private static final String BUCKET_NAME = "order-event-test-bucket";
 
  // ... actual test
}

You can use this as a template to now prepare your AWS Lambda function.

Regarding the source code upload, I guess it makes sense to mount or copy the .zip version of your Lambda also to your container to then access it inside.

A NodeJS lambda could be created like the following:

awslocal lambda create-function \
    --region eu-central1 \
    --function-name your-api \
    --runtime nodejs8.10 \
    --handler lambda.apiHandler \
    --memory-size 128 \
    --zip-file fileb://api-handler.zip

Upvotes: 2

George Ivanov
George Ivanov

Reputation: 31

I've managed to setup Localstack and a MySql testcontainer (in groovy spock) like so:

  @Shared
  public static Network network = Network.newNetwork()

  @Shared
  @ClassRule
  public static MySQLContainer mySql = new MySQLContainer(DockerImageName.parse('mysql:5.7'))
    .withNetwork(network)
    .withNetworkAliases(MY_SQL_HOST_NAME) as MySQLContainer

  @Shared
  @ClassRule
  public static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse('localstack/localstack:0.11.6'))
    .withServices(SQS, LAMBDA, CLOUDWATCH, DYNAMODB, KMS, STS, IAM)
    .withNetwork(network)
    .withEnv('LAMBDA_DOCKER_NETWORK', network.name)
    .withCopyFileToContainer(MountableFile.forHostPath(new File('the jar file').path), '/opt/code/localstack/lambda.jar')
    .withCopyFileToContainer(MountableFile.forClasspathResource('seed.yaml'), '/init/seed.yaml')
  • This sets up a bridged network so that the two containers can communicate with each other. Also setting LAMBDA_DOCKER_NETWORK allows the lambda to communicate with MySql, SQS etc.
  • The lambda jar file is copied to the container (withCopyFileToContainer)
  • I also needed KMS, so seed.yaml initialises it (see Local KMS documentation)

Once the container is up and running the lambda can be deployed using awslocal and standard CLI commands:

    def result = localStack.execInContainer(
      'awslocal', 'lambda', 'create-function',
      '--function-name', 'lambda-name',
      '--runtime', 'java8',
      '--handler', 'uk.co.blah.blah.Handler',
      '--role', 'arn:aws:iam::123456:role/irrelevant',
      '--zip-file', 'fileb://lambda.jar',
      '--environment', "Variables={MY_SQL_USERNAME=${mySql.username},MY_SQL_PASSWORD=${encryptedDbPassword},PLATFORM_DB_URL=${"jdbc:mysql://${MY_SQL_HOST_NAME}:3306"}," +
        "AWS_REGION=${localStack.region}}"
    )

Finally, the lambda can be manually triggered in tests like so:

localStack.execInContainer('awslocal', 'lambda', 'invoke', '--function-name', 'lambda-name', 'output0.json')

Upvotes: 1

Related Questions