Component Tests
https://frontendmasters.com/courses/intermediate-react-v2/component-tests/
Testing SearchParams
// **** __test__/SearchParams.test.js ****
import React from "react";
import { render, fireEvent, cleanup } from "react-testing-library";
// Jest know that these to be mocked ↴
import pet, { _breeds, _dogs, ANIMALS } from "@frontendmasters/pet";
import SearchParams from "../SearchParams";
// a Jest function:
afterEach(cleanup);
test("SearchParams", async () => {
// it will render in NodeJS, no jsdom or else used
// make sure that This is a component (`<MYComponent/>`)
const { container, getByTestId, getByText } = render(<SearchParams />);
// the convention is to add a `data-test` attribute
// on your DOM element
const animalDropdown = getByTestId("use-dropdown-animal");
// testing that all items are inserted plus one for the "all" option.
expect(animalDropdown.children.length).toEqual(ANIMALS.length + 1);
// writing the second test
expect(pet.breeds).toHaveBeenCalled();
const breedDropdown = getByTestId("use-dropdown-breed");
expect(breedDropdown.children.length).toEqual(_breeds.length + 1);
// a more advanced test
// we will render,
const searchResults = getByTestId("search-results");
// test the resulting text on the "page"
expect(searchResults.textContent).toEqual("No Pets Found");
// then siulate a click (fireEvent from `react-testing-library`)
fireEvent(getByText("Subit"), new MouseEvent("click"));
// then see if it gets the data back from the API
// note, that there's a discrepancy between the async
// call in the real component
// versus the mock implementation which is synchronous
// solution: he just changed to synchronous implementation
// in the real component
// instead of monkey patching the promises API
expect(pet.animals).toHaveBeenCalled();
expect(searchResults.children.length).toEqual(_dogs.length);
expect(container.firstChild).toMatchInlineSnapshot(`..long DOM tree here`);
});
Jest render in NodeJS without jsdom
It's from react-testing-library
, that allows us to render in NodeJS without any slow tricks, like headless Chrome, jsdom, etc.
Make sure that this is a component (<MYComponent/>
):
const { container, getByTestId, getByText } = render(<SearchParams />);
Accessing your DOM elements in tests
Brian recommends adding data-test
attributes and using these to target DOM elements in your tests. It's better decouple your tests and other logic.
The you can access your target with getByTestId
. It's coming from the react-testing-library
, render
method.
const animalDropdown = getByTestId("use-dropdown-animal");
--- a/src/useDropdown.js
+++ b/src/useDropdown.js
@@ -8,6 +8,7 @@ const useDropdown = (label, defaultState, options) => {
{label}
<select
id={id}
+ data-testid={id}
value={state}
onChange={e => updateState(e.target.value)}
onBlur={e => updateState(e.target.value)}
Can also finally add tests to the test
script in package.json.
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/preset-env": "^7.4.3",
"@babel/preset-react": "^7.0.0",
+ "@types/jest": "^24.0.9",
"babel-eslint": "^10.0.1",
"cross-env": "^5.2.0",
"eslint": "^5.12.1",
@@ -22,8 +23,10 @@
"eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-hooks": "^1.0.2",
+ "jest": "^24.1.0",
"parcel-bundler": "^1.12.1",
- "prettier": "^1.16.1"
+ "prettier": "^1.16.1",
+ "react-testing-library": "^6.0.0"
},
"scripts": {
"clear-build-cache": "rm -rf .cache/ dist/",
@@ -31,7 +34,10 @@
"dev:mock": "cross-env PET_MOCK=mock parcel src/index.html",
"format": "prettier --write \"src/**/*.{js,jsx}\"",
"lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "jest",
+ "test:coverage": "jest --coverage",
+ "test:watch": "jest --watch",
+ "test:update": "jest -u"
},
"author": "Brian Holt <btholt+complete-intro-to-react@gmail.com>",
"license": "Apache-2.0",
Async implementation vs sync mock
The implementation uses async
and await
, the mock is a synchronous function.
For now, the easier solution was to remove the async
implementation.
Brian says that he had some discussions about this with the React team, who also uses a lot of async
and await
and they said that they are working on a better solution...
--- a/src/SearchParams.js
+++ b/src/SearchParams.js
@@ -12,16 +12,16 @@ const SearchParams = () => {
const [animal, AnimalDropdown] = useDropdown("Animal", "dog", ANIMALS);
const [breed, BreedDropdown, updateBreed] = useDropdown("Breed", "", breeds);
- async function requestPets() {
- const { animals } = await pet.animals({
- location,
- breed,
- type: animal
- });
-
- console.log("animals", animals);
-
- setPets(animals || []);
+ function requestPets() {
+ pet
+ .animals({
+ location,
+ breed,
+ type: animal
+ })
+ .then(({ animals }) => {
+ setPets(animals || []);
+ });
}
useEffect(() => {
Finally
Add the data-test
id on the target
--- a/src/Results.js
+++ b/src/Results.js
@@ -3,7 +3,7 @@ import Pet from "./Pet";
const Results = ({ pets }) => {
return (
- <div className="search">
+ <div className="search" data-testid="search-results">
{!pets.length ? (
<h1>No Pets Found</h1>
) : (