Functional Tests with .Net Core and LocalStack
By: Oğuzhan Çağlıyan
Tags: Localstack, Functional Testing, 0.11.5, Dotnet Core, Engineering

A couple of months ago we started developing a new social media feature as Armut. Its basically a content feed that you can interact with your neighbors to communicate an important issue, to ask for help, etc. We haven’t decided its name, so in this article, we will call it the “Neighborhood” project. After specified the requirements of the project, we decided to use the following Amazon Services.

As Armut, all of our infrastructures have been running on AWS for a long time. We have been writing functional tests intensively as a team for a long time. While developing this application, we found very few articles on writing functional tests for .Net with Localstack and we wanted to share our own experiences.

I will show you from a sample project how to use LocalStack in Functional Tests with SQS and DynamoDB. The sample project will create a movie for DynamoDb, bring this movie with its ID, comment on this movie, but the commenting part will be putting items in SQS.

Why we used LocalStack?

LocalStack provides an easy-to-use test/mocking framework for developing Cloud applications.

Localstack is a super cool tool for mocking AWS and writing functional tests. You can use DynamoDB, SQS, S3, SNS, etc services at the container level and create resources with LocalStack, and most of the features are free. At Armut, we use Localstack heavily for Lambda and other AWS services. A couple of days ago LocalStack released the 0.11.5 version and we will use it in the article.

As Armut, we have been developing and maintaining a .Net client for LocalStack you can find the repo from https://github.com/localstack-dotnet/localstack-dotnet-client

The 0.11.5 version has breaking changes, service URLs fully changed, If you are using an older version you need to change service URLs.

Functional Tests

As Armut Engineering Team, we attach great importance to functional tests. Projects whose functional tests are not written are incomplete projects for us. While we created our functional test code base, we combined https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1 directives and these tools

Before start coding, you should install Docker, .Net Core 3.0 version on your computer.

TestContainers

Testcontainers is a library that supports testing, providing lightweight, throwaway instances of common databases, etc. that can run in a Docker container.

At first, we had a hard time developing functional tests with LocalStack. The reason for this was that we had to deal with YAML files, we were trying to do all the settings, port operations, which services should be selected through these YAML files. As you know, YAML files are always difficult to deal with, the easiest way to overcome these difficulties was to use TestContainers.

Project Architecture

First, we need to create our Fixture and Collection classes. We will use XUnit’s CollectionDefinition class and ICollectionFixture interface for this purpose.

Sometimes you will want to share a fixture object among multiple test classes. The database example used for class fixtures is a great example: you may want to initialize a database with a set of test data, and then leave that test data in place for use by multiple test classes. You can use the collection fixture feature of xUnit.net to share a single object instance among tests in several test class.

ApiTestCollection inherits from ICollectionFixture<LocalStackFixture> and ICollectionFixture<TestServerFixture>. We also mark this class with CollectionDefinition attribute.

Let’s create LocalStackFixture and TestServerFixture classes.

LocalStackFixture class inherits from IAsyncLifetime that provide async lifetime support for the test classes that you can do whatever you want at InitializeAsync and DisposeAsync methods.

InitializeAsync, called immediately after the class has been created, before it is used. DisposeAsync, called when an object is no longer needed. Called just before Dispose if the class also implements that.

The TestcontainersContainer class provides us to create a new container from the latest image of LocalStack with some properties.

WithCleanUp removes a stopped container automatically.

removes a stopped container automatically. WithEnvironment sets an environment variable in the container e. g. -e, --env "test=containers" .

sets an environment variable in the container e. g. . WithPortBinding publishes a container port to the host e. g. -p, --publish 80:80 .

With the new version of Localstack, all services started to be hosted from port 4566. But, if you are using the previous version of LocalStack you should use legacy service ports. (see https://github.com/localstack/localstack/tree/v0.10.9 for old ports).

Let’s create TestServerFixture class

TestServiceFixture class inherits from WebApplicationFactory that is configuring the Startup and Program classes.

We used CreateHostBuilder for

Applying appsettings.json to project

Reaching default ConfigureServices and Configure methods

We used ConfigureWebHost for

Getting default services and manipulate them for our purpose

Seeding DynamoDB or other Amazon Services

On ConfigureWebHost you should change the default implementation of Amazon Services. Because they will be trying to reach your AWS credentials and cause an exception. So the solution should be:

Remove from DI and configure it with your configuration.

Set your fake awsAccessKeyId and awsSecretAcessKey in our case we set both of them “123”

ServiceURL setter should be after the RegionEndpoint setter because if you dive into Amazon.Runtime.ClientConfig class you can see RegionProperty’s setter will set serviceUrl as a null value. Thus, you have to set ServiceUrl after RegionEndpoint.

Great! We finished our configuration and we can start seeding DynamoDB and SQS.

Seeding DynamoDB

Our scenario assumes that we have a Movie entity that has a HashKey called “DirectorId” as a Guid, RangeKey called “CreateDate” as a string and GlobalSecondaryIndexes called “MoiveTableMovie-Index” that has MovieId as a HashKey.

DynamoDbSeeder is responsible for creating DynamoDB tables and records( See the TestServerFixture line 79 to see how we use it)

Before running our functional tests, we need to make sure that our DynamoDB table is created and filled with appropriate data. To do that we will create seeder classes and run them right before running the functional tests.

Seeding SQS

SqsSeeder is responsible for creating SQS Queues(See the TestServerFixture line 81 to see how we use it )

Let’s create Fifo Queue and related DLQ on SQS

Scenarios

Project workflow following these rules

Check simply RequestViewModel is valid or not

Map RequestViewModel to Model. We used mapster instead of automapper. Compared to Mapster with AutoMapper it is a much more effective tool for mapping. If you haven’t tried it before, I suggest you give it a shot.

Call the related service

To check the service model is valid or not, we used FluentValidation.

Service calls to the related repository class.

API can add data to DynamoDB and gets it from it.

Let’s create the “Movie” controller.

MovieService responsible for

Check parameters and models

Call the repository

Add the movie

Comment to the movie

Base repository and repository implementation

Let’s create the test class

Previously we have talked about ApiTestCollection and you can see how we used it as a Collection.

For some checks and utils, we needed to create BaseScenario class

After running the tests, you can see that all the scenarios run successfully.

dotnet test

You can also run tests with the test runners of IDEs. I ran it from the console to show the output.

Last Words

Since we started to write functional tests with LocalStack, we have almost stopped testing manually, testing times have been shortened, bugs are decreased significantly, started a deep understanding of how to AWS work.

You can find GitHub repo from:

I hope I could help.