As part of a recent Cloud consultancy project, nubeGo Technical Consultant, Dave Button, has been building and testing GraphQL APIs using AWS AppSync. In this blog post, Dave explains how to automatically unit test and integration test AppSync APIs from within an AWS CodePipeline CI/CD configuration.
What is AppSync?
AppSync is the AWS managed service for defining and building serverless GraphQL APIs. At nubeGo we build GraphQL APIs to service web applications and services, querying data from both serverless storage (such as S3 and Aurora) and traditional data stores. AppSync makes this particularly straightforward when integrating directly with an Aurora Serverless database.
As we wanted to unit test, and integration test the logic within our AppSync queries, we decided to move our business logic layer out of AppSync and encapsulate our database queries and calculation logic inside AWS Lambda functions. By creating individual Lambdas that are invokable outside of AppSync, this allows us to test the logic in isolation of the API framework.
How do I perform Continuous Integration using AWS CodePipeline?
We use CodePipeline for Continuous integration and deployment. For this project, the source code was stored inside AWS CodeCommit and, every time a commit or a merge is made to the master branch, the pipeline is triggered.
We use a pipeline that comprises 4 stages:-
Fetches the source code from the code repository, triggered by commits to the branch.
Using an AWS CodeBuild project, this stage constructs the serverless AppSync application ready for deployment and executes code-level unit tests.
Deploys a “test” version of the AppSync API and runs automated integration tests against this, before destroying the test application.
Creates a CloudFormation changeset containing only the changes to the production application and deploys this into the “production” AppSync environment.
Our tests take place in both the Build and Test phases (see below). You can execute tests of any type by defining the test commands inside your buildspec.yml file. For our unit tests, we use the Mocha test framework and the buildspec looks a little bit like this:-
Notice the reports element at the bottom of the spec, this tells CodeBuild where to find the output from your test results, so that you can view them in the console when a build runs.
What should I test? And how?
When testing an AppSync API, we perform two different types of tests.
Purpose: These test individual code routines or Lambda functions to ensure that they achieve what the developer expects, and to protect against regression.
Framework: Implemented using the Mocha test unit for nodeJS, the Chai assertion library and Sinon for mocking and stubbing.
Location: Unit test results can be accessed from the Pipeline build stage or the CodeBuild project on the “reports” tab.
Coverage: We aim for a minimum of 80% code coverage.
Unit test results can be viewed from within the AWS console, or exported for analysis elsewhere.
Purpose: These test end-to-end API methods through common business scenarios to ensure that they achieve the functional requirements, and to protect against functional regression.
Framework: Implemented using the Mocha test unit for nodeJS and the Chai assertion library.
Location: Integration test results can be accessed from the Pipeline test stage or the associated CodeBuild project on the “reports” tab.
Coverage: We don’t yet capture coverage for integration tests.
End-to-end integration tests are most powerful when they’re run against a representative data source, similar to (if not exactly the same as) your production environment. When you’re building a test framework as part of a CI/CD pipeline you might want to consider the following:-
Automatically provisioning a test data source equivalent to (if not exactly the same as) your production data source
Database / data source versioning strategies for migration and upgrade of database as code. Tools such as Liquibase and Flyway can help here.
Automatically creating a cut-down version of the pipeline whenever a new feature branch is created. This ensures that development of new features can be tested in the same way as the master branch, helping to reduce the feedback loop time for integration issues.
Find out more