Testing Strategy - HelpRefugees/project-flamingo GitHub Wiki
This project has been developed using a test-driven development (TDD) methodology. We have been using an outside-in ("London school") approach to writing tests.
When implementing a new feature, we would suggest proceeding as follows:
End-to-End
Firstly, convert the acceptance criteria in the story you're delivering into
end-to-end (E2E) tests. The current E2E tests can be found in e2e/integration
.
We are using Cypress to develop and run the E2E
tests.
Helper functions
We have provided two Cypress commands (see e2e/support/commands
) to make
testing easier:
-
cy.seed(filename)
- load the database with data from the specified file, assumed to be ine2e/data
. This file should be a JSON file containing a single object whose keys are collection names and whose values are arrays of documents to insert into that collection. Thereports
andusers
collections will be emptied before loading this data and, if no users are specified in the JSON, the default users listed ine2e/data/loader.js
will be added. -
cy.login(username[, password])
- log the specified user in, using their default password if one isn't supplied.
If your test involves hitting the email webhook, you can use
cy.request("DELETE", `${Cypress.env("WEBHOOK")}/_calls`);
to clear out any
existing data.
Page objects
We are using the page object
paradigm to write our tests, using the page objects located in e2e/pages
to
add a layer of abstraction between the behaviour we're testing and the details
of interacting with the DOM.
You can extend the BasePage
and provide a path
property or accessor to get
access to basic visit
(navigate to that page) and isAt
(assert that we're on
that page) functionality for your new pages.
As many of the DOM elements use a data-test-id
attribute, there's a handy
function in e2e/pages/helpers.js
named testId
to generate a selector for
that attribute, e.g. testId("my-id")
would generate
"[data-test-id=\"my-id\"]"
.
Server
API Integration
If your feature requires new behaviour from the API, we suggest moving on to an
integration test. You can find the current tests in server/tests
with the
suffix _integration.test.js
. We are using Jest to run
these tests, and SuperTest to make
HTTP assertions.
Database
The server tests run in an environment where the Mongo database client is
available as global.DATABASE
, you can use this to set preconditions or make
assertions on postconditions.
Authentication
Many of your requests will need authentication to succeed, which needs cookies
to be set. The easiest way to do this is to access the agent
directly, make a
POST
to log in then make the request you want, e.g.:
const agent = request.agent(app);
await agent
.post("/api/login")
.send({ username, password })
.expect(200);
// use agent.method for additional requests
Server Unit
Testing at the API boundary makes it easier to refactor the server code, but sometimes more granularity (and less set up) is useful. If the integration tests don't give you sufficient confidence in the details of your functionality, or you find that you're writing a lot of them for a single endpoint, it makes sense to move down to the unit level.
For example, see the unit tests in server/tests/reports_service.test.js
. These
are also run using Jest.
Client
Client Unit
If you're writing new client functionality, this should be covered with unit
tests. The client unit tests, also using Jest, live alongside the modules they
exercise with the suffix .test.js
.
Component tests use the Enzyme testing utility. Try
to use the shallow
mount wherever possible to reduce complexity and isolate
the components more effectively.
Linting
Additionally we run automated linting and, for the client code, Flow type analysis. Linting keeps the code layout and styling consistent and reduces noise in the commit diffs.
Additional Reading
You may find the following resources useful if you're new to automated testing and TDD: