Developer Tests
- Unit tests:
yarn unit-test [-u]
- Intergration tests:
yarn int-test [-u]
- Browser integration tests:
cypress run
-u
: flag to update snapshots.
QA Tests
- User journey tests:
yarn ui-test
Definitions, guidlines and templates for our testing process.
Tests start with the most inner pure components and progressively enhance as the they grow in complexity and size.
Unit tests
Every every individual component (Enzyme). Testing of individual functions or classes by supplying input and making sure the output is as expected.
Headless integration test
Testing how multiple components and processes intergrate with eachother and behave as expected. (Enzyme)
Browser integration test
If it gets too complex to mock the browser environment e.g modals using React Proxy use cypress to load a browser.
User journey test
Testing scenarios on the product itself, by controlling the browser or the website, regardless of the internal structure to ensure expected behavior. (Selenium)
Tests, snapshots and mock data are placed in a __tests__
folder. This is so they don't get included in the build.
.
├── __tests__
| ├── __snapshots__
| └── input.unit.tsx
| └── radio.unit.tsx
├── Input.tsx
├── Radio.tsx
To allow us to run tests seperatly file names are suffixed with the relevant test type:
.unit.tsx
unit tests.int.tsx
integration tests.ui.tsx
user interface tests
To run a test individually or folder of tests run yarn jest
followed by dir path.
Generally pure units of code with a single function, typcially; components, services and helpers. Provide all these units with both correct and edge case inputs and make sure their outputs are as expected using the assertion functions. These tests should be fast and lightweight to run.
Automated: pre-commit
By definition, integration tests test the integration between the various components of an application. Test's are mocked in a browser like environment and are typically done on the container
or app
level which contain several components. To run these tests mock a browser like environment by mounting containers using Enzyme.
Automated: on build (locally) and automated in circle-ci when a PR is merged
In some cases it can become over complicated to re-create how two (or more) components interact with eachother in a browser like environment. For example, the search box in the header which triggers a modal using React Portals from the footer or mocking breakpoints with using the JSS context provider. In such cases its more efficient to use an actual browser environment to mock these interactions. For these cases we use Cypress.io. Cypress can be ran in two different ways:
User journey tests are manually ran and managed by the QA team. The key difference with these tests is they aren't concerend with any implementation details and are not thought of in terms of components but rather key journeys that need to be completed. These are implemented using Selenium with the automated browserstack intergration which will be able to test browsers and devices.
These tests are located in the __tests__
folder in the root directory and are suffixed with the file extension ui
.
To make tests easier to manage and maintain tests are seperated into pages or components e.g header, login page. Rather than full user journeys (end-to-end) which can result in tests overlapping and lack of confidnece on what is actually being testsed.
yarn ui-test
will run all tests locally using chrome (default)yarn ui-test --chrome
will run them in chromeyarn ui-test --browserstack
will send all tests to automate.browserstack.comnode node_modules/mocha/bin/mocha ./__test__/login-modal.ui.js
to run a test individually
The selenium tests currently support Chrome and Browserstack drivers. To add more install the any new drivers to the machine you're testing on and add the configuration to the browsers
object in the _config.js
file. To select which driver you want the tests to run with just add the corresponding flag to the run script e.g yarn ui-test --firefox
if you've added a firefox driver.
Reducing any logic and selectors in the test file will make it easier to read and manage. We do this by creating a seperate class with all the methods to complete any functionality or selecting elements on that page to a seperate file and import that as a class. These files live in either the pages
or compoments
folders in the __tests__
.
For the automated unit and intergration tests in the build process we use the following tools;
- Jest (testing structure, assertions, snapshots, mocks, spies, and stubs)
- Enzyme (mounting and rendering components)
- Cypress (automated browser integration tests)
User journey tests use
- Selenium
- Browserstack (functional e2e tests and screenshots)
We have agreed to use the data attribute data-testid
which is the convention used by cypress and react-testing-library. It should be used sparingly as an escape-hatch for when there isn't a target to get an element with and transfersing the DOM would make the tests to brittle.
Various components such as buttons and links have a prop of testId
which you can use to apply it to a component. However, its best to use it on containers and parent elements when possible to avoid bloating the code and also means the test ID's are more generic and can be shred accross multiple tests.
The cypress folder can be found in the root of the application. Good read about Asserting About Elements
cypress open
- Interactive Mode: provides a useful UI for developing testscypress run
- Command Line: for automation or watching in the background
Run a spesific tests add this flag: --spec cypress/integration/onesite-gatsby/news.tsx
This refers to the organization of your tests. We try and encourage a BDD structure that supports behavior-driven development (BDD). A high level overview would look something like this:
describe('Login', function() {
// describes a module with nested "describe" functions
describe('enter user name', function() {
// specify the expected behavior
it('should have a minium of 3 characters', function() {
//Use assertion functions to test the expected behavior
...
})
it('should not accept spaces', function() {
//Use assertion functions to test the expected behavior
...
})
})
})
Use this for unit and intergration tests.
// arrange
var repository = Substitute.For<IClientRepository>();
var client = new Client(repository);
// act
client.Save();
// assert
mock.Received.SomeMethod();
Always use simple statements. Loops and conditionals must not be used. If they are, you add a possible entry point for bugs in the test itself.
Remember, unit tests are a design specification of how a certain behaviour should work, not a list of observations of everything the code happens to do.
If a method has several end results, each one should be tested separately. Whenever a bug occurs, it will help you locate the source of the problem.