Jest Tests
Jest Tests are used to test JavaScript code. They run in Node and can work in the following workspace types:
- Expo
- Node 14
- React 16
- React 18
Test Structure
Folder Structure
Jest tests execute a .test.js file that is injected into the learner's workspace. Authors do not need to add this file.
Jest can be configured via a Babel configuration file and jest.config.js file. The contents of these files depends on the test and workspace type.
Folder Structure: React
In React 18 workspaces, .babelrc is provided with the necessary presets. Authors do not need to add this file (link to file):
{
"presets": [
"@babel/preset-env",
["@babel/preset-react", {"runtime": "automatic"}]
]
}
jest.config.js is also provided to use a jsdom environment that supports rendering React components. Authors do not need to add this file (link to file):
module.exports = {
testEnvironment: 'jsdom'
};
Folder Structure: Expo
In Expo workspaces, additional configuration must be provided in each workspace by the author:
babel.config.js must include the Babel-Expo preset:
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
jest.config.js must include the Jest-Expo preset and–if using the library–set up the Jest Native Testing Library:
module.exports = {
preset: "react-native",
setupFilesAfterEnv: [ "@testing-library/jest-native/extend-expect"],
};
Test File Structure
Jest tests are defined with test or its alias it. Assertions within are made with expect:
// Import assertion libraries and React components
import TestRenderer from 'react-test-renderer';
import MyComponent from './MyComponent';
// Provide an error message
test('MyComponent has my Hello', () => {
// Render components and make assertions
const component = TestRenderer.create(<MyComponent />);
expect(component.root.findByProps({className: "intro"}).children).toEqual(['Hello']);
});
Jest supports describe blocks to group tests, which you'll need to use if you want any setup (e.g. beforeAll) or teardown (e.g. afterAll):
import React from 'react';
import { View } from 'react-native';
import { render, cleanup } from '@testing-library/react-native/pure';
import App, { styles } from './App';
describe(App, () => {
let error;
let viewLayout;
let viewBoxes;
beforeAll(() => {
try {
([viewLayout, ...viewBoxes] = render(<App />).UNSAFE_queryAllByType(View));
} catch (err) {
error = err;
}
});
afterAll(() => {
cleanup();
});
it('`App` should render without errors, double-check for syntax errors.', () => {
expect(error).toBeUndefined();
});
it('`App` should render a `View` component, containing three other `View` components.', () => {
expect(viewLayout).toBeTruthy();
expect(viewBoxes).toHaveLength(3);
});
});
Available Test Helpers
Additional JavaScript packages are available to learners in the Learning Environment. The specific packages available will vary with each workspace type, as determined by their package.json in EIN. For example, here is React 18's package.json. Here is a set of common packages:
| Package | Use Case |
|---|---|
| @testing-library/react | Testing utilities for React components |
| chai | Many helpful general assertions |
| enzyme | React testing |
| jsdom | Building pure-JS representations of the DOM based on learner code |
| msw | API mocking library |
| react-test-renderer | Render React components to pure JavaScript objects, without depending on the DOM |
| rewire | Patch in learner code files to access local, not exported variables |
| sinon | Building mocks & stubs of functions |
| sinon-chai | Chai extension for sinon asertions |
| structured | Run tests based on parsing code structure rather than execution or behavior |
| supertest | API endpoint testing |
Tests execute in Node environment, so any Node APIs are available as well, such as fs, path, etc. Node versions may differ across workspace types, e.g. React 18 uses Node 16.
Example Tests
This test uses React Testing Library to render a component, click on a button, then make an assertion on its contents.
// The component to test
import React, { useState } from "react";
const App = ({name}) => {
const [visibleName, setVisibleName] = useState("");
const handleClick = () => {
setVisibleName(name);
}
return (
<div>
<button onClick={handleClick}>Show name</button>
{visibleName}
</div>
)
};
export default App;
// Checkpoint test
import { fireEvent, render, screen } from '@testing-library/react';
import App from './src/App';
it('App renders data prop when button is clicked', () => {
render(<App name="abcdef" />);
const button = screen.getByRole('button');
fireEvent.click(button);
screen.getByText("abcdef");
});
Within React Testing Library, there are many ways to select elements. Sometimes the vanilla JS selectors such as .querySelector and .textContent are sufficient. Other times RTL's query methods are preferable. This test shows both.
import { render, screen } from "@testing-library/react";
import React from "react";
import App from "./src/App";
describe("", () => {
it("Your app must render a button with the text `Show name`", () => {
const { container } = render(<App />);
// Vanilla JS selectors
// A "button" may be an <input> with type submit or a <button>
const inputButton = container.querySelector('input[type=submit]');
const buttonButton = container.querySelector('button');
const button = inputButton || buttonButton
expect(button).toBeTruthy();
expect(button.textContent).toMatch(/show name/i);
// OR
// RTL's query methods
screen.getByRole('button', { name: /show name/i });
});
})
This test uses React Test Renderer to render and query the contents of the MyComponent component.
import TestRenderer from 'react-test-renderer';
import MyComponent from './MyComponent';
import SubComponent from './SubComponent';
it('MyComponent contains a SubComponent with prop `bar` and an element with class `sub` containing "Sub"', () => {
const testRenderer = TestRenderer.create(<MyComponent />);
const testInstance = testRenderer.root;
expect(testInstance.findByType(SubComponent).props.foo).toBe('bar');
expect(testInstance.findByProps({className: "sub"}).children).toEqual(['Sub']);
});
This assumes we have MyComponent and SubComponent components:
import SubComponent from './SubComponent';
export default function MyComponent() {
return (
<div>
<SubComponent foo="bar" />
<p className="my">Hello</p>
</div>
)
}
export default function SubComponent() {
return (
<p className="sub">Sub</p>
);
}
Other Guidelines
- Actual code (real variable names, language syntax, etc. ) in feedback messages should be code-ticked (`)
- Jest tests should follow Test standards.