People do mistakes, that's normal.
Tests help you identify mistakes in earlier stages which makes fix much more easy.
Code is a subject to change. When changing, it's easy to forget update something related or break something else.
Tests prevent accidental bugs.
Writing tests, you will eventually use the code. If the code is hard to test - this is a spot that code will be hard to use.
Tests highlight bad code smells.
Writing tests, you are thinking about edge cases - errors, fails and other crap happening in a real life.
Tests make you think more about your code.
Business want to deliver features as fast as possible, better - yesterday. Manual testing is slow. No testing is risky.
Tests find bugs fast. Everybody happy.
Unit tests exists to test a single unit behavior in full isolation
const calculate = (income, percentage) => income * percentage
const Greeter = ({name})=> hello {name}
Integration tests exists to check, how does your components interact with each other.
import { calculatePercentage } from './percentage-calculator';
import { calculateFullPrice } from './price-calculator';
export function someComplexCalculations() {
const percentage = calculatePercentage(5000, 25);
return calculateFullPrice(1000_000, percentage);
}
UI tests - usually tests some workflow, but in isolation from the real data.
Cypress, Playwright are good tools to help you with
E2e tests testing the whole business scenario. From page load to desired action. Backend, DataBase are included
E2E tests that the whole application works as expected
const add = (a,b)=> a+b;
const sum = add(1,1);
expect(sum).toBe(2)
const App = () => <div>Hello word</div>;
export default App;
import { render, screen } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import App from "./App";
test("hello world should be rendered", () => {
act(()=> { render(<App />); }); // act
const greeting = screen.getByText(/hello world/i); // assert
expect(greeting).toBeInTheDocument(); // assert
});
This unit tests checks existence of the text on the screen
That is how the tests works.
Test which never fail is a bad test
type MoneyFormatterProps = {
amount: number;
};
export const Money = ({ amount }: MoneyFormatterProps) => (
<>{amount.toFixed(2)}</>
);
import { render, screen } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { Money } from "./money.formatter";
test("Money should format 50 with two decimals - 50.00",()=> {
const amount = 50; //arrange
act(()=>{ render(<Money amount={amount} />);}); //act
const formatted = screen.getByText("50.00");
expect(formatted).toBeInTheDocument(); //assert
});
Is 0.00 desired behavior?
This is how tests works - giving you hints!
import { render, screen } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { Btn } from "./Btn";
it("when btn clicked, callback should be called", () => {
const spy = jest.fn();
act(()=> { render(<Btn onClick={spy}>Test</Btn>);})
screen.getByText("Test").click();
expect(spy).toHaveBeenCalled();
});
npm i react-test-renderer @types/react-test-renderer -D
import { Btn } from "./Btn";
import renderer from "react-test-renderer";
it("snapshot", () => {
const tree = renderer.create((
<Btn onClick={() => null}>Test</Btn>)
).toJSON();
expect(tree).toMatchSnapshot();
});
Comparing pixel to pixel with master snapshot
export const greeting = () => `hello: `;
import { greeting } from "./greeter";
type Props = { name: string };
export const AppGreetings = ({ name }: Props) => (
<>{`${greeting()}${name}`}</>
);
type Props = { name: string; greeter: () => void };
export const AppGreetings = ({ name, greeter }: Props) => (
<>{`${greeter()}${name}`}</>
);
jest.mock("./greeter", () => ({
__esModule: true,
greeting: () => "mocked",
}));
it("greetings should work", () => {
act(() => {
render(<AppGreetings name="testName" />);
});
expect(screen.getByText("mockedtestName"))
.toBeInTheDocument();
});
Obviously, to write a test, you need some time. And in this moment, the new feature you just wrote, is waiting
Usual business don't want to wait till you write a test
Code is subject to change which means that tests are subject to update. Updating tests costs your time, and money for business
You need to cover a lot of edge cases, you need to cover different combinations, you need to split components into the smallest parts. Laziness is also a reason.
It depends. If you are writing one-time script or a tiny obvious app - feel free to skip testing
If the app is subject to grow and requires maintenance, or it touches sensitive things like money or health - please test
You should always consider, what if something went wrong - network, user input, etc
Don't test the implementation - test only results that are important to the business
Ideally you should have a one assert in the test. It's not something a must but having massive assertions will lead to harder maintenance