Tips for day-to-day development.
When implementing or updating an API route, your workflow should be:
- Make sure to write the OpenAPI documentation for the route (or update it).
- Write the tests for the route (or update them). See testing tips below.
- Implement the route:
-
If you need new configuration from the environment, it should be retrieved in
config/index.js
. You should not useprocess.env
anywhere else.Also add your new variables to the
.env.sample
file. -
If you need to make changes to the database, write a database migration. See the relevant section in the development guide and in this document.
-
Add appropriate logs, or update existing logs if necessary. See Logging.
When refactoring a legacy route, also mind the following practices. This should be done after the documentation and tests have been written.
-
Errors should generally not be handled in the controllers but passed to Express so that they end up in the global error handler.
- If the client needs additional information about the error other than a
simple message, use an instance of
HttpProblemDetailsError
so that the response is in our standard error format. If this type of error occurs in multiple places, add a convenience function to create such an error. Seeapp/utils/errors.js
for examples.
- If the client needs additional information about the error other than a
simple message, use an instance of
-
If the implementation is a large function, attempt to split it into simpler, easier-to-understand and easier-to-test functions.
-
If the implementation contains Node.js-style callbacks, refactor to use promises. If a callback is required, isolate it into a function that simply converts the call into a promise.
-
If the route is not RESTful, make a note of suggested changes in
TODO.md
. -
When implementing test data fixtures, make a note in
TODO.md
of database schema improvements that could be made, or constraints that could be added (e.g. missing foreign keys or columns that are nullable but should not be). -
If the route does not perform proper authorization, make a note of suggested security improvements in
TODO.md
.
When writing a new migration:
- If you have added or removed a table, you may need to update the lists of
database tables in
spec/utils/db.js
, as well as theresetDatabase
function. - If you have added or removed a column, you may need to update the relevant
test fixtures in
spec/fixtures
.
In general:
- Mock any external service with costly or undesirable side-effects, such as
sending emails. This will speed up the test suite; the faster it is, the less
you will hesitate to run it. It will also avoid mistakenly sending emails to
actual people. The
createApplicationWithMocks
function is meant to create an instance of the application with all external services mocked for testing.
When writing an API route test:
-
Verify the entire HTTP response body and any custom headers, not just one or two JSON properties. This ensures that the behavior of the API does not unexpectedly change in the future. The
jsonBody
,httpProblemDetailsBody
andvalidationErrors
assertions have been implemented for this purpose. -
Verify that the API implementation and the OpenAPI documentation are in sync by using the
matchRequestDocumentation
assertion on the request you are sending, and thematchResponseDocumentation
assertion on the response you receive. -
Wipe all data in the test database using the asynchronous
resetDatabase
function before any test which depends on the database, and insert any data fixtures you might need before running the actual test. This ensures that tests always start with a specific, reproducible state, and that they do not interfere with each other. -
Verify that no unexpected side effects have occurred by using the
expectSideEffects
andexpectNoSideEffects
functions. This may help detect unwanted side effects when making changes in the future, such as unexpected database changes.Note that these functions do not account for every change that could have occurred. They can currently detect:
-
Changes in the number of rows in the application's main database tables.
A row that is inserted and then deleted will not be detected as a change. Updates are not detected either.
-
Mails that are sent using the application's mail service.
Additional checks for side effects may need to be performed in complex tests.
-
-
Verify that the correct data is in the database after an insertion, update or deletion when testing a route which modifies the database. Even if the API returns the correct response, it does not prove that the database was actually modified or that it was modified correctly. Common database checks (e.g. whether a specific user is in the database) may be encapsulated into convenience functions in the
spec/expectations
directory.
- If the route requires authentication, write a test which ensures that public access is denied.
- If the route has authorization, write a test which ensures that unauthorized access is denied.
For tests on GET
routes that simply retrieve information:
- Write a base test that inserts data fixtures and retrieves them.
- Write tests for collection filters. If time permits, there should be one test per filter, and one test using a combination of filters (if applicable).
- For routes that retrieve a single resource, write a test to ensure that a 404 Not Found response is returned if an unknown resource is requested.
- For routes that retrieve a collection, write a test to retrieve the empty list without any data in the collection, to ensure that the system is functional in its initial state.
For tests on PATCH
, POST
and PUT
routes:
- Write a base test which checks that the route works with a minimal request (with all optional properties omitted).
- Write a test which checks that the route works with all optional properties included (if any).
- Write one test which triggers various validation errors. Additional tests might be warranted if there are complex validations.
- Write one test for each unicity constraint to ensure that they work.
- If the route uses external services, write a test to check what happens when the service fails.
For tests on DELETE
routes:
- Write a base test which checks that the entity is no longer present in the database once it has been deleted.
- Write a test to ensure that a 404 Not Found response is returned if an unknown resource is deleted.