Implement functional tests for serverless architectures
Important: this article is outdated ! This is not the correct way to run functional tests on lambda/dynamoDB projects. There are faster and more reliable way to do it without launching a serverless-offline process.
I recently had to write functional tests for a NodeJS serverless application.
The setup process is not that obvious so I decided to post an article here to explain how to get started.
As you can see, we spawn here a child process to start serverless offline on a given host/port. You can change these settings if you have multiple serverless “microservices” and you want to run tests concurrently. To make sure the server is correctly started we watch for Offline listening on in the command stdout(“feels like a hack but it works”).
The spawned process is stored in global.__offline__ variable, which is also available in teardown hook. Thus to stop the server after all tests are executed, simply put this in the teardown hook test/hooks/teardown.ts:
Now you are ready to write yout first functional test !
Write your first test
You can use a HTTP client like got or a dedicated test library such as supertest to perform request on the serverless offline server you just have spawned.
describe('The order create handler - POST /orders', () => { test('should return 422 if product ID is missing', (done) => { req('orders', { body: JSON.stringify({ customerId: 'customer@example.com', }), }).then(() => { fail('should not return 200'); }) .catch((err) => { expect(err.statusCode).toBe(422); done(); }); }); test('should return 422 if customer ID is missing', (done) => { req('orders', { body: JSON.stringify({ productId: '110e8400-e29b-11d4-a716-446655440000', }), }).then(() => { fail('should not return 200'); }) .catch((err) => { expect(err.statusCode).toBe(422); done(); }); }); test('should return 422 if customer ID is not a valid email', (done) => { req('v4/access-requests', { body: JSON.stringify({ productId: '110e8400-e29b-11d4-a716-446655440000', customerId: 'invalidCustomer', }), }).then(() => { fail('should not return 200'); }) .catch((err) => { expect(err.statusCode).toBe(422); done(); }); }); test.todo('Should return 400 if product does not exist in database'); test.todo('Should return 400 if customer does not exist in database'); test.todo('Should return 200 if everything is OK'); // etc });
Use a custom authorizer
If your handler uses a custom authorizer, you need to mock the authorizer lambda return value. Here is a serverless.yml example that impelments such a mechanism.
To mock the authorization process, add a “test-auth” function in your yaml. Then set the real or the mocked authorizer function that is called in your endpoints depending on your process.env.NODE_ENV.
We simply authenticate user making the request as admin or non-admin base on two-values mocked token: user-token or admin-token. Then we format the response to meet AWS custom authorizer requirements.
The real authentication would decode a JWT and perform a database request to provide context and current logged in user.
Now you have to update your functional tests and send the token in your requests: