Integration tests for AWS serverless solution
Tags: serverless, aws, dotnet, testing
Published: 2023-05-15 12:28:05

## Motivation
1. Add integration tests without fakes for AWS services.
2. Run tests locally and in a build machine without AWS accounts.

## Application architecture
We have a typical serverless solution - lambda that is triggered by AWS resource and store result in the S3 bucket. It ia two popular AWS serverless services.

## Integration tests solution
You need to make sure that Lambda put documents into an S3 bucket. This implementation consists of the next concepts:
* [xUnit](https://xunit.net/) unit tests tool
* lambda with configured dependecy injection
* s3 bucket that runs in docker using [LocalStack](https://github.com/localstack/localstack)

### Run container
To launch a container in code we will use [Testcontainers](https://github.com/testcontainers/testcontainers-dotnet). Testcontainers is a library that is built on top of the .NET Docker remote API and provides a lightweight implementation to support your test environment in all circumstances. This library supports pre-defined packages for containers or you can use your `.dockerfile`. We will use a pre-defined package for [LocalStak](https://www.nuget.org/packages/Testcontainers.LocalStack). LocalStack is a cloud service emulator that runs in a single container for AWS service. LocalStack supports a growing number of AWS services.

```
var container = new LocalStackBuilder()
  .Build();
```

### Configure xUnit with container

You need to start the container before the tests run and stop after the tests run. To do that we will use two features *IAsyncLifetime* and *IClassFixture*. And next, add access to the container(local URLs) in tests.

```
public class LocalStackFixture : IAsyncLifetime
{
  public readonly LocalStackContainer Container =
    new LocalStackBuilder().Build();

  public Task DisposeAsync()
    => container.StopAsync();
  
  public Task InitializeAsync()
    => container.StartAsync();
}

public class LambdaHandlerTest : IClassFixture<LocalStackFixture>
{
  private readonly LocalStackFixture fixture;

  public LambdaHandlerTest(LocalStackFixture localStackFixture)
    => fixture = localStackFixture;
}
```

### Initialize AWS service
You need to have a pre-defined S3 bucket, before putting documents. LocalStck has built-in AWS CLI(`awslocal`). The `awslocal` command has the same usage as the `aws` command. For detailed usage, please refer to the man pages of aws help, but of course, it has some [limitations](https://docs.localstack.cloud/user-guide/integrations/aws-cli/#limitations) :wink:. That means, we just need to run create bucket command and start running the test when a command is completed.
```
public LocalStackContainer Container = new LocalStackBuilder()
  .WithStartupCallback((container, ct) => 
    container.ExecAsync(new[] 
    {
     "awslocal", "s3api", "create-bucket", "--bucket", BUCKETNAME 
    }, ct))
  .Build();
```
### Lambda Tests
The final test with all configurations will look like that
```
[Fact]
public async Task HandleShoudPutDocToS3Test()
{
  var client = new AmazonS3Client(new AmazonS3Config 
  { 
    ServiceURL = fixture.Container.GetConnectionString() 
  });

  var sut = new LambdaHandler(collection => collection
    .AddSingleton<IAmazonS3>(client)
    .PostConfigure<Config>(c => c.BucketName =
       LocalStackFixture.BUCKETNAME));

  var docId = Guid.NewGuid().ToString();
  await sut.Handle(new TestLambdaContext 
  {
    AwsRequestId = docId 
  });

  var res = await client.GetObjectAsync(
    LocalStackFixture.BUCKETNAME,
    $"{docId}.txt");
  _ = res.HttpStatusCode.Should().Be(HttpStatusCode.OK);
}
```

## Conclusions
1. To run tests locally or in the build machine we only need to install docker and run standard tests command `dotnet test`.
2. Testcontainers -  support a loot of pre-defined packages that you can use the integration tests.
3. LocalStack makes development easier and cheaper when work with AWS services.

And finally, the source code in the GitHub repository
{% github ohalay/serverless-containers %}

## Help links
- [LocalStack](https://github.com/localstack/localstack)
- [Testcontainers](https://github.com/testcontainers/testcontainers-dotnet)