Automating test and deployment of serverless projects with GitHub Actions
September 17, 2020
Last week’s “Off-by-none” serverless newsletter featured a link to automating application deployment using GitHub Actions. It’s a great run-through of what needs to be set up in order to do AWS deployments from a GitHub workflow.
Inspired by Rajan’s post I decided to build a demo project of my own, in Node.js and with the Serverless Framework as the IaC- and deployment tool. I also wanted to expand the scope a little by introducing automated tests so that the resulting workflow resembles a basic but complete CI/CD pipeline, running solely on GitHub.
So I put together an example project with a single Lambda backed by an integration test. This implies invoking the handler function from code and letting it interact with real services, e.g. via the AWS SDK, to validate that our use of downstream services is working as expected.
The testing framework is Jest and a basic test looks like this:
describe(`Invoking endpoint GET /`, () => {
it(`Should return a message`, async () => {
const response = await invokeFunction('index', {})
expect(response.statusCode).toEqual(200)
expect(response.body).toHaveProperty('message')
})
})
GitHub workflow file
The workflow file, after installing Node.js in the runner environment, simply exercises a few npm
and serverless
commands.
Remember that by installing the Serverless Framework CLI as a dev dependency we get to use this great framework without risking incompatible versions between environments? Another reason the npm run sls -- deploy
script comes in handy is that it reduces the number of steps in our workflow jobs. Node.js, with npm, is all the runner needs to build, test and deploy our serverless project.
Now, as jobs in a worflow by default run in parallel, it’s important to make the deploy-to-staging
job depend on integration-test
to succeed by using the needs
keyword. This will make the jobs run sequentially as well as skip deployment if the test job does not complete successfully.
Tbe gist of the workflow file is as follows:
... on push and pull request on master
env:
AWS_REGION: us-east-1
AWS_STAGE_CI_TEST: ci-test
AWS_STAGE_STAGING: staging
NODE_VERSION: "12"
jobs:
integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Deploy to stage "${{ env.AWS_STAGE_CI_TEST }}"
run: npm run sls -- deploy --region $AWS_REGION --stage $AWS_STAGE_CI_TEST
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Run integration tests
run: npm run integration-test
... remove from AWS_STAGE_CI_TEST
deploy-to-staging:
needs: integration-test
runs-on: ubuntu-latest
steps:
... checkout, setup-node, install deps like above
- name: Deploy to stage "${{ env.AWS_STAGE_STAGING }}"
run: npm run sls -- deploy --region $AWS_REGION --stage $AWS_STAGE_STAGING
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
The referenced secrets are encrypted environment variables created in the repository or organization.
You can find the complete example here.
References
- https://offbynone.io/ - Weekly Serverless Newsletter by Jeremy Daly
- https://rajanpanchal.net/automate-application-deployment-using-github-actions/ - Inspiring post
- https://jestjs.io/ - A delightful JavaScript Testing Framework
- https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions - Workflow syntax for GitHub Actions
Testing techniques learned from attending Yan Cui’s “Production-Ready Serverless” workshop. I really recommend this workshop for anyone wanting to up their AWS serverless skills.