Skip to content

Instantly share code, notes, and snippets.

@Jezfx
Last active November 20, 2019 07:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jezfx/bd5a02fb44f7d2b3053b89517c9141b7 to your computer and use it in GitHub Desktop.
Save Jezfx/bd5a02fb44f7d2b3053b89517c9141b7 to your computer and use it in GitHub Desktop.
testing framework WIP

Scripts

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

Testing process

Definitions, guidlines and templates for our testing process.

TLDR;

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)

Working together

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.

Unit tests

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

Headless integrates tests

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

Browser Integration Tests

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:

UI / UX Journey Tests

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.

Organising Tests

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.

Running the tests

  • yarn ui-test will run all tests locally using chrome (default)
  • yarn ui-test --chrome will run them in chrome
  • yarn ui-test --browserstack will send all tests to automate.browserstack.com
  • node node_modules/mocha/bin/mocha ./__test__/login-modal.ui.js to run a test individually

Adding Drivers

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.

Page Object Modal

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__.

Tools

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)

Test ID

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.

Cypress

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 tests
  • cypress run - Command Line: for automation or watching in the background

Run a spesific tests add this flag: --spec cypress/integration/onesite-gatsby/news.tsx

Testing structure

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
       ... 
    })   
  })
})

Conventions & good practices

Use arrange, act and assert (AAA) pattern

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();

Avoid logic in your tests

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.

Don't write unnecessary expectations

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.

Don't test multiple concerns in the same test

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment