가장 간단한 방법 정확하게 맞는 경우를 찾을때.
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
// object값을 비교할때
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
//반대의 경우 .not.toBe
test('adding positive numbers is not zero', () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0);
}
}
});
undefined, null and false를 구분하고 싶을때, 혹은 그러지않을때 쓸 수 있다.
- toBeNull matches only null
- toBeUndefined matches only undefined
- toBeDefined is the opposite of toBeUndefined
- toBeTruthy matches anything that an if statement treats as true
- toBeFalsy matches anything that an if statement treats as false
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
숫자를 비교함.
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); This won't work because of rounding error
expect(value).toBeCloseTo(0.3); // This works.
});
문자열 비교
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('the shopping list has milk on it', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
function compileAndroidCode() {
throw new Error('you are using the wrong JDK!');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// You can also use a string that must be contained in the error message or a regexp
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
// Or you can match an exact error mesage using a regexp like below
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK$/); // Test fails
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK!$/); // Test pass
});
더 많은 메소드는 공식 문서에서 확인가능
https://jestjs.io/docs/expect
// 반드시 return이 필요하다.
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
// 이렇게도 쓸 수 있겠다!
return expect(fetchData()).resolves.toBe('peanut butter');
// error가 발생해서 'error'를 리턴한다고 했을때
return expect(fetchData()).rejects.toBe('error');
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
If done() is never called, the test will fail (with timeout error), which is what you want to happen.
// done을 반드시 사용해야한다.
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
테스트 코드를 여러개 작성하면 값이 계속 누적되어 원하는대로 테스트가 안될 경우 사용할 수 있다.
let num = 10;
// test코드가 실행되기 전에 num을 0으로 만들어주고 코드가 실행됨
beforeEach(() => {
num = 0
})
// test코드가 실행된 후에 num을 0으로 만들어주고 코드가 실행됨
afterEach(() => {
num = 0
})
전체 테스트 코드가 실행되기 전후에 실행됨. describe안에 각각 동작함.
let user;
// 모든 코드가 실행되기 전에 한번 유저 정보를 받는다.
beforeAll(() => {
user = getUserDb()
})
// 모든 테스트 코드가 실행되고 유저 정보를 지운다.
afterAll(() => {
return deleteUserDb()
})
특정 테스트 코드만 실행.
test.only("0 + 5 = 5", () => {
expect(add(0, 5)).toBe(5);
});
mocking은 단위 테스트를 작성 시, 해당 코드가 의존하는 부분을 가짜(mock)로 대체하는 기법을 말한다. 일반적으로 테스트하려는 코드가 의존하는 부분을 직접 생성하기가 어려운 경우 또는 부담스러운 경우에 mocking을 많이 사용한다.
// forEach 함수를 테스트해보려고한다.
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
// 이럴때 fn()을 사용해서 mock function을 만들어 쓸 수 있음.
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);
// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
// .mock property
// The function was called exactly once
expect(someMockFunction.mock.calls.length).toBe(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');
// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);
// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toBe('test');
// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
const filterTestFn = jest.fn();
// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
실제 api hitting없이 fake response를 리턴하고 테스트
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
expect().toMatchSnapshot();
// Home.tsx
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import LoginForm from "../components/LoginForm";
import styles from "../styles/Home.module.css";
const Home: NextPage = () => {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<LoginForm onSubmit={() => console.log("submit")} />
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
);
};
export default Home;
// home.test.tsx
import { render, screen } from "@testing-library/react";
import Home from "../pages/index";
import "@testing-library/jest-dom";
describe("Home", () => {
it("Home에 header가 렌더 됐는지 확인한다.", () => {
render(<Home />);
const heading = screen.getByRole("heading", {
name: /welcome to next\.js!/i,
});
expect(heading).toBeInTheDocument();
});
});
// LoginForm.tsx
import { useState } from "react";
interface Props {
onSubmit: (email: string, password: string) => void;
}
export default function LoginForm({ onSubmit }: Props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return (
<form onSubmit={() => onSubmit(email, password)}>
<label>
email
<input
type="email"
placeholder="user@test.com"
value={email}
onChange={({ target: { value } }) => setEmail(value)}
/>
</label>
<label>
password
<input
type="password"
value={password}
onChange={({ target: { value } }) => setPassword(value)}
/>
</label>
<button disabled={!email || !password}>login</button>
</form>
);
}
// LoginForm.test.tsx
import React from "react";
import "@testing-library/jest-dom";
import { render, fireEvent } from "@testing-library/react";
import LoginForm from "../../components/LoginForm";
describe("<LoginForm />", () => {
it("이메일과 패스워드를 전부 입력하면 버튼이 활성화된다.", () => {
const { getByText, getByLabelText } = render(
<LoginForm onSubmit={() => null} />
);
const button = getByText("login");
const email = getByLabelText("email");
const password = getByLabelText("password");
expect(button).toBeDisabled();
fireEvent.change(email, { target: { value: "user@test.com" } });
fireEvent.change(password, { target: { value: "Test1234" } });
expect(button).toBeEnabled();
});
it("버튼을 누르면 submit이 실행된다", () => {
const onSubmit = jest.fn();
const { getByText, getByLabelText } = render(
<LoginForm onSubmit={onSubmit} />
);
const button = getByText("login");
const email = getByLabelText("email");
const password = getByLabelText("password");
fireEvent.change(email, { target: { value: "user@test.com" } });
fireEvent.change(password, { target: { value: "Test1234" } });
fireEvent.click(button);
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
더 많은 테스팅 라이브러리 정보는 공식 문서를 참조하자
https://testing-library.com/docs/react-testing-library/example-intro
https://jestjs.io/docs/getting-started
https://www.daleseo.com/react-testing-library/