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 in e2e/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. The reports and users collections will be emptied before loading this data and, if no users are specified in the JSON, the default users listed in e2e/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: