Testing Guide

You can throw paint against the wall and eventually you might get most of the wall, but until you go up to the wall with a brush, you'll never get the corners. - JB RainsBerger

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/dfaa1cf7-26b6-440f-8ec9-c6e64a1c14f9/Untitled.png

End to End

End-End tests are click through tests that typically hit both the front-end and the back-end.

Characteristics:

  • Driven from the perspective of the user
  • Slow Feedback Loop (creation, debugging, pipeline)
  • High Value (If done correctly)
  • High Cost (maintenance, data, time)

Test Example at a High level

  1. go to lowes.com
  2. select search element
  3. click search element
  4. enter keys {product}
  5. select first result
  6. click first result
  7. wait for content to load
  8. expect product img to be correct

Popular E2E Test Runners/Libraries

Cypress

TestCafe

Protractor

Playwright

Where E2E Testing Goes Wrong

  • Porting all manual test cases to E2E test cases because "automation"
  • Unreliable data && || data reliant expectations
  • Non-linear test suite execution flow (ie repeating navigation in child describe)
  • Leniency on best coding practices
  • Perceiving Peer Review as less important than dev code
  • Using selectors like .classname[index] or xpath in place of more resilient selectors such as Text or #testId

Static, Unit and Integration Tests

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8c51a3c5-2a4b-4c8f-ad9b-1fc66a7dc619/Untitled.png

Understanding Atomic Design Principals

BUT WHY? Aspire to reduce the coupling between components in your library to a minimum and increase the reusability of components as much as possible.

*Atoms:*

Basic building blocks of matter, such as a button, input or a form label. They’re not useful on their own.

*Molecules:*

Grouping atoms together, such as combining a button, input and form label to build functionality.

*Organisms:*

Combining molecules together to form organisms that make up a distinct section of an interface (i.e. navigation bar)

*Templates:*

Consisting mostly of groups of organisms to form a page — where clients can see a final design in place.

*Pages:*

An ecosystem that views different template renders. We can create multiple ecosystems into a single environment — the application.

Integration Tests

Atomic Reference: Molecules, Organisms,Templates

"Write tests. Not too many. Mostly integration." - Guillermo (Next.js Creator)

Description: Integration Tests often render multiple react components or functions with all of the providers used in the app. A common theme with Integration tests is to avoid mocking

Example:

Components, action creators, selectors, and reducers are like atoms that when combined create a connected component molecule. By testing a molecule you can verify the connections between its atoms.

import React from "react";
// this module is mocked via jest's __mocks__ directory feature
import axiosMock from "axios";
import { render, generate, fireEvent } from "til-client-test-utils";
import { init as initAPI } from "../utils/api";
import App from "../app";
beforeEach(() => {
  window.localStorage.removeItem("token");
  axiosMock.__mock.reset();
  initAPI();
});
test("login as an existing user", async () => {
  const {
    getByTestId,
    container,
    getByText,
    getByLabelText,
    finishLoading,
  } = render(<App />);
  // wait for the app to finish loading the mocked requests
  await finishLoading();
  fireEvent.click(getByText(/login/i));
  expect(window.location.href).toContain("login");
  // fill out form
  const fakeUser = generate.loginForm();
  const usernameNode = getByLabelText(/username/i);
  const passwordNode = getByLabelText(/password/i);
  usernameNode.value = fakeUser.username;
  passwordNode.value = fakeUser.password;
  // submit form
  const { post } = axiosMock.__mock.instance;
  const token = generate.token(fakeUser);
  post.mockImplementationOnce(() =>
    Promise.resolve({
      data: { user: { ...fakeUser, token } },
    })
  );
  fireEvent.click(getByText(/submit/i));
  // wait for the mocked requests to finish
  await finishLoading();
  // assert calls
  expect(axiosMock.__mock.instance.post).toHaveBeenCalledTimes(1);
  expect(axiosMock.__mock.instance.post).toHaveBeenCalledWith(
    "/auth/login",
    fakeUser
  );
  // assert the state of the world
  expect(window.localStorage.getItem("token")).toBe(token);
  expect(window.location.href).not.toContain("login");
  expect(getByTestId("username-display").textContent).toEqual(
    fakeUser.username
  );
  expect(getByText(/logout/i)).toBeTruthy();
});

Unit Tests

Atomic Reference: Atoms, Molecules

Two opposing views:

  • Rendering to the DOM with react
  • Using shallow rendering

View: Rendering to the DOM with react

  • React-Testing-Library

    • You want your tests to avoid including implementation details of your components and rather focus on making your tests give you the confidence for which they are intended.
    • As part of this, you want your testbase to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down.
    import "@testing-library/jest-dom/extend-expect";
    import React from "react";
    import { render } from "@testing-library/react";
    import ItemList from "../item-list";
    // Some people don't call these a unit test because we're render to the DOM with React.
    // They'd tell you to use shallow rendering instead.
    // When they tell you this, send them to <https://kcd.im/shallow>
    test('renders "no items" when the item list is empty', () => {
      const { getByText } = render(<ItemList items={[]} />);
      expect(getByText(/no items/i)).toBeInTheDocument();
    });
    test("renders the items in a list", () => {
      const { getByText, queryByText } = render(
        <ItemList items={["apple", "orange", "pear"]} />
      );
      // note: with something so simple I might consider using a snapshot instead, but only if:
      // 1. the snapshot is small
      // 2. we use toMatchInlineSnapshot()
      expect(getByText(/apple/i)).toBeInTheDocument();
      expect(getByText(/orange/i)).toBeInTheDocument();
      expect(getByText(/pear/i)).toBeInTheDocument();
      expect(queryByText(/no items/i)).not.toBeInTheDocument();
    });
    

View: Using Shallow Rendering

http://atomicdesign.bradfrost.com/images/content/instagram-atomic.png