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
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
- go to lowes.com
- select search element
- click search element
- enter keys {product}
- select first result
- click first result
- wait for content to load
- expect product img to be correct
Popular E2E Test Runners/Libraries
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
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
- 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
- Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren't indirectly asserting on behavior of child components.
import { shallow } from "enzyme"; import sinon from "sinon"; import Foo from "./Foo"; describe("<MyComponent />", () => { it("renders three <Foo /> components", () => { const wrapper = shallow(<MyComponent />); expect(wrapper.find(Foo)).to.have.lengthOf(3); }); it("renders an `.icon-star`", () => { const wrapper = shallow(<MyComponent />); expect(wrapper.find(".icon-star")).to.have.lengthOf(1); }); it("renders children when passed in", () => { const wrapper = shallow( <MyComponent> <div className="unique" /> </MyComponent> ); expect(wrapper.contains(<div className="unique" />)).to.equal(true); }); it("simulates click events", () => { const onButtonClick = sinon.spy(); const wrapper = shallow(<Foo onButtonClick={onButtonClick} />); wrapper.find("button").simulate("click"); expect(onButtonClick).to.have.property("callCount", 1); }); });
Static Tests
Tools like Eslint, Spellchecker, Prettier
Eslint:
- ESLint statically analyzes your code to quickly find problems. ESLint is built into most text editors and you can run ESLint as part of your continuous integration pipeline.
- Many problems ESLint finds can be automatically fixed. ESLint fixes are syntax-aware so you won't experience errors introduced by traditional find-and-replace algorithms.
- Preprocess code, use custom parsers, and write your own rules that work alongside ESLint's built-in rules. You can customize ESLint to work exactly the way you need it for your project.
Spell Checker:
- VS Code plugin used to check for correct spelling
Prettier:
- An opinionated code formatter
- Supports many languages
- Integrates with most editors
- Has few options
SIDE-NOTE: I Hope that we add Prettier to our stack in the future
References:
- https://testing-library.com/docs/react-testing-library/intro
- https://airbnb.io/enzyme/docs/api/shallow.html
- https://eslint.org/
- https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker
- https://prettier.io/docs/en/why-prettier.html
- https://itnext.io/react-redux-integration-tests-with-jest-enzyme-df9aa6effd13
- https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests
- https://itnext.io/component-vs-ui-integration-vs-e2e-tests-f02b575339dc
- https://devexpress.github.io/testcafe/
- https://github.com/GoogleChrome/puppeteer
- http://atomicdesign.bradfrost.com/chapter-2/
- https://www.cypress.io/
http://atomicdesign.bradfrost.com/images/content/instagram-atomic.png