Manual Testing : Writing code preview and test in browser (still error-prone because it is hard to test all scenarios).
Automated Testing : Testing with Code that tests your code. Test individual building blocks of app -> very technical and allows you to check all building blocks.
Unit test ( most important ) : Testing individual building blocks in isolation. Projects typically contain more than dozens of unit tests.
Integration tests : Testing the combinations of mutiple building blocks.
End to End test (E2E) : Testing complete scenarios in your app as the user might experince it.
What : The smallest building blocks that make up your app.
How : Test success and error cases, also test rare results.
Running test and asserting the results : 'Jest'
Simulating (rendering) our react app : 'React Testing Library'
These are already installed with npx install create-react-app
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
First argument is test identifier and second is actual testing code.
To run testing, execute this command line(depends on your 'scripts' in pacakge.json)
npm test
If test is successful, you can see this result
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.615 s
Ran all test suites.
You can write all test code in App.test.js, but actually it is better to create separated test code file close to actual building which is tested by code.
For example,
Products.js <-> Products.test.js
Here is three 'A' we need to check.
1. Arrange : set up the test data, test conditions and test enviroment
2. Act : Run logic that should be tested (e.g execution of functions)
3. Assert : Compare execution results with expected functions
const Greeting = () => {
return (
<div>
<h2>Hello world!</h2>
<p>Let's Test</p>
</div>
);
};
export default Greeting;
import { render, screen } from "@testing-library/react";
import Greeting from "./Greeting";
test("renders Hellow World as a text", () => {
// arrangement
render(<Greeting />);
//act
//assert
const helloWorldEl = screen.getByText("Hello World!", { exact: false });
expect(helloWorldEl).toBeInTheDocument();
});
You can group tests related to specific component with global function 'describe()'
define what this test is for and give actual test code as a function argument
describe("Greeting component", () => {
test("renders Hellow World as a text", () => {
// arrangement
render(<Greeting />);
//act
//assert
const helloWorldEl = screen.getByText("Hello World!", { exact: false });
expect(helloWorldEl).toBeInTheDocument();
});
});
You should identify all the possible scenarios and clarify it in test code.
const Greeting = () => {
const [changedText, setChangedText] = useState(false);
const changeTextHandler = () => {
setChangedText(true);
};
return (
<div>
<h2>Hello world!</h2>
{!changedText && <p>Let's Test</p>}
{changedText && <p>text changed</p>}
<button onClick={changeTextHandler}>Change Text!</button>
</div>
);
};
describe("Greeting component", () => {
test("renders Hello World as a text", () => {
// arrangement
render(<Greeting />);
//act
//assert
const helloWorldEl = screen.getByText("Hello World!", { exact: false });
expect(helloWorldEl).toBeInTheDocument();
});
test("renders let's test when state false", () => {
render(<Greeting />);
const LetsTestEl = screen.getByText("Let's Test", { exact: false });
expect(LetsTestEl).toBeInTheDocument();
});
test("renders text changed when state true", () => {
render(<Greeting />);
//act
const buttonEl = screen.getByRole("button");
userEvent.click(buttonEl);
//asserting
const ChangedTextEl = screen.getByText("text changed", { exact: false });
expect(ChangedTextEl).toBeInTheDocument();
});
test("Not render let's test if the button was clicked", () => {
render(<Greeting />);
//act
const buttonEl = screen.getByRole("button");
userEvent.click(buttonEl);
//asserting
const ChangedTextEl = screen.queryByText("Let's Test", { exact: false });
expect(ChangedTextEl).toBeNull();
});
});
For example, when you want to test whether list items is existed, you can use aysnchronous find 'findAllByRole'(There are more methods).
import { render, screen } from "@testing-library/react";
import Async from "./Async";
describe("Async Component", () => {
test("renders post request", async () => {
render(<Async />);
const listItemElements = await screen.findAllByRole("listitem");
expect(listItemElements).not.toHaveLength(0);
});
});
By the way, For more information about HTML elements role, Read this article.
HTML element role
But , Regarding to http requests, it is not ideal send requests whenever we test the component(It will cause heavy network traffic).
Instead of that you send real requests, you can set mock function in test code.
Then this mock function imitate our real fetch process , so we can check whether there are list items or not without sending real request.
describe("Async Component", () => {
test("renders post request", async () => {
window.fetch = jest.fn();
window.fetch.mockResolvedValueOnce({
json: async () => [{ id: "p1", title: "First post" }],
});
render(<Async />);
const listItemElements = await screen.findAllByRole("listitem");
expect(listItemElements).not.toHaveLength(0);
});
});