Post

Jest - matcher

๐Ÿ“Œ์‹œ์ž‘ํ•˜๋ฉฐ

์—ฐ๋ง์— ๊ฐ€๋ณ๊ฒŒ ์ง„ํ–‰ํ•œ ํ”„๋กœ์ ํŠธ์—์„œ Jest๋ฅผ ๊ฐ„๋‹จํžˆ ์ ์šฉํ•ด๋ณด์•˜๋‹ค. ์—ญ์‹œ ์ฒ˜์Œ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์ด๋‹ค ๋ณด๋‹ˆ ์‹œ๊ฐ„๋„ ๋งŽ์ด ์†Œ์š”๋˜๊ณ  ์‚๊ทธ๋• ๊ฑฐ๋ฆฌ๊ธฐ๋Š” ํ–ˆ์ง€๋งŒ ๊ทธ๋ž˜์„œ ๊ทธ๋Ÿฐ์ง€, PASS๊ฐ€ ๋œฐ ๋•Œ๋งˆ๋‹ค ๋„ˆ๋ฌด ๊ธฐ๋ป์„œ ๋‹ค๋“ค ๋ด๋‹ฌ๋ผ๊ณ  ํ•˜๊ณ  ์‹ถ์—ˆ๋‹คโ€ฆ ๐Ÿคฃ

์–ด์จŒ๋“ , ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ƒˆ๋กญ๊ฒŒ ๋ฐฐ์šด Jest์˜ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด๋ฉด์„œ ๋ณต์Šตํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

โœ…๋ฐฐ์—ด ๊ฒ€์ฆ

  • ๋ฐฐ์—ด ์•ˆ์— ํŠน์ • ๊ฐ’์ด ํฌํ•จ๋˜์–ด์žˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ๋Š” toContain
  • ๋ฐฐ์—ด ๊ธธ์ด๋ฅผ ํ™•์ธํ•  ๋•Œ๋Š” toHaveLength
1
2
3
4
5
const friends = ["์‹ ์งฑ๊ตฌ", "๊น€์ฒ ์ˆ˜", "ํ•œ์œ ๋ฆฌ"]
it("friends ๋ฐฐ์—ด์„ ๊ฒ€์‚ฌํ•œ๋‹ค.", () => {
  expect(friends).toContain("์‹ ์งฑ๊ตฌ")
  expect(friends).toHaveLength(3)
})

โœ…๊ฐ์ฒด ๊ฒ€์ฆ

  • ๊ฐ์ฒด์— ์›ํ•˜๋Š” ๊ฐ์ฒด์˜ ์ผ๋ถ€๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ๋Š” toMatchObject
  • ๊ฐ์ฒด์— ํ•ด๋‹น ํ‚ค ๊ฐ’์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•  ๋•Œ๋Š” toHaveProperty
1
2
3
4
5
6
7
const jjanggu = { name: "์‹ ์งฑ๊ตฌ", class: "ํ•ด๋ฐ”๋ผ๊ธฐ" }
it("jjanggu ๊ฐ์ฒด๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.", () => {
  expect(jjanggu).toMatchObject({ name: "์‹ ์งฑ๊ตฌ" })
  expect(jjanggu).toMatchObject({ name: "์‹ ์งฑ๊ตฌ", class: "ํ•ด๋ฐ”๋ผ๊ธฐ" })
  expect(jjanggu).toMatchObject({ name: "์‹ ์งฑ๊ตฌ", class: "์žฅ๋ฏธ" }) //Fail
  expect(jjanggu).toHaveProperty("name")
})

์งฑ๊ตฌ๋Š” ํ•ด๋ฐ”๋ผ๊ธฐ ๋ฐ˜์ด๊ณ  ์žฅ๋ฏธ ๋ฐ˜์ด ์•„๋‹ˆ๋ฏ€๋กœ, ์•„๋ž˜ ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•œ๋‹ค.

1
expect(jjanggu).toMatchObject({ name: "์‹ ์งฑ๊ตฌ", class: "์žฅ๋ฏธ" }) //Fail

โœ…mocking

ํ•จ์ˆ˜ (utils)๋ฅผ ๋ชจํ‚นํ•˜๋Š” fn, mock, spyOn์˜ ์“ฐ์ž„์ƒˆ๋ฅผ ์•Œ์•„๋ณด์ž!

โžก๏ธfn

  • ๋…๋ฆฝ์ ์ธ ํ•จ์ˆ˜๋ฅผ ๋ชจํ‚นํ•œ๋‹ค.
  • API์˜ ์„ฑ๊ณต, ์‹คํŒจ ์‚ฌ๋ก€๋ฅผ ํ…Œ์ŠคํŠธ ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์›๋ณธ ํ•จ์ˆ˜๋ฅผ importํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
it("sum ํ•จ์ˆ˜๋ฅผ jest.fn์œผ๋กœ ๋ชจํ‚นํ•ด๋ณธ๋‹ค.", () => {
  const mockSum = jest.fn((a: number, b: number) => {
    if (a === 1 && b === 2) return 10
    if (a === 3 && b === 4) return 20
    return 0
  })

  expect(mockSum(1, 2)).toBe(10)
  expect(mockSum).toHaveBeenCalledWith(1, 2) // ํ•จ์ˆ˜ ์ธ์ž๊ฐ€ 1, 2๋กœ ํ˜ธ์ถœ๋จ

  expect(mockSum(3, 4)).toBe(20)
  expect(mockSum(5, 6)).toBe(0)
  expect(mockSum).toHaveBeenCalledTimes(3) // ํ•จ์ˆ˜๊ฐ€ ์„ธ ๋ฒˆ ํ˜ธ์ถœ๋จ
})

โžก๏ธmock

  • ํŠน์ • ๋ชจ๋“ˆ ์ „์ฒด๋ฅผ ๋ชจํ‚นํ•œ๋‹ค.

next.js์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” next/navigation์„ ๋ชจํ‚นํ•ด, pathname์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž.

1
2
3
4
5
6
7
8
9
10
"use client"
import React from "react"
import { usePathname } from "next/navigation"

const Path = () => {
  const pathname = usePathname()
  return <div>{pathname}</div>
}

export default Path
  1. jest.mock๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด next/navigation์„ ๋ชจํ‚นํ•œ๋‹ค.
  2. ๋ชจํ‚นํ–ˆ์„ ๋•Œ์˜ ๊ฒฐ๊ณผ๊ฐ’์€ mockReturnValue๋ฅผ ์ด์šฉํ•ด ์„ค์ •ํ•œ๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { render, screen, act } from "@testing-library/react";
import Path from "./Path";
import { usePathname } from "next/navigation";

//๋ชจ๋“ˆ์„ ๋ชจํ‚นํ•œ๋‹ค.
jest.mock("next/navigation", () => ({
  usePathname: jest.fn(),
}));

it("ํ˜„์žฌ path๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋œ๋‹ค.", () => {
  // ์ดˆ๊ธฐ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  (usePathname as jest.Mock).mockReturnValue("/origin-route");

  const { rerender } = render(<Path />); //๋ฆฌ๋ Œ๋”๋ง์„ ์ˆ˜๋™์œผ๋กœ triggerํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
  screen.debug();

  // ํ™”๋ฉด์— '/origin-route' ๊ฒฝ๋กœ๊ฐ€ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  expect(screen.getByText("/origin-route")).toBeInTheDocument();

  // ๊ฒฝ๋กœ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค.
  //๋ฆฌ๋ Œ๋”๋ง์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด act๋กœ ๊ฐ์‹ผ๋‹ค.
  act(() => {
    (usePathname as jest.Mock).mockReturnValue("/new-route");
  });

  // ๊ฒฝ๋กœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
  // ์ˆ˜๋™์œผ๋กœ rerender triggerํ•ด์ค€๋‹ค.
  rerender(<Path />);
  screen.debug();

  expect(screen.getByText("/new-route")).toBeInTheDocument();
});

โžก๏ธspyOn

  • ๋ชจ๋“ˆ์ด๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ์‹œํ•˜๊ฑฐ๋‚˜ ๋ชจํ‚นํ•œ๋‹ค.

    • ์ด๋•Œ์˜ โ€˜๊ฐ์‹œโ€™ ๋Š” ํ˜ธ์ถœ ์—ฌ๋ถ€, ์‚ฌ์šฉ๋˜๋Š” ์ธ์ˆ˜ ๋“ฑ ํ•ด๋‹น ๋ชจ๋“ˆ์˜ ํ๋ฆ„์„ ์‚ดํŽด๋ณด๊ณ  ์‹ถ์„ ๋–„ ์‚ฌ์šฉํ•œ๋‹ค.\
  • jest.spyOn์€ ๊ฐ์ฒด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ์‹œํ•œ๋‹ค.

1
2
3
export default function sum(a: number, b: number): number {
  return a + b
}

์ด๋Ÿฌํ•œ sumํ•จ์ˆ˜๋ฅผ ๊ฐ์‹œํ•ด๋ณด์ž.

1
2
3
4
import sum from "./sum"

jest.spyOn(sum) //๊ฐ์‹œ ๋ถˆ๊ฐ€
jest.spyOn({ sum }, "sum") // ์ •์ƒ ์ž‘๋™

:star: ์œ„์— ์ž‘์„ฑํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ, jest.spyOn๋Š” ๊ฐ์ฒด๋ฅผ ๊ฐ์‹œํ•˜๊ธฐ ๋–„๋ฌธ์—, jest.spyOn(sum)์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, jest.spyOn({ sum }, "sum") ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž„์‹œ๋กœ ๊ฐ์ฒด ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ฐ์‹œํ•ด์•ผ ํ•œ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
it("sum ํ•จ์ˆ˜์— spyOn์„ ์ ์šฉํ•ด ๋™์ž‘์„ ๊ฒ€์ฆํ•œ๋‹ค.", () => {
  // sum ํ•จ์ˆ˜์— spyOn์„ ์ ์šฉ
  const sumSpy = jest.spyOn({ sum }, "sum")

  // sum ํ•จ์ˆ˜ ํ˜ธ์ถœ
  const result = sum(3, 7)

  // ํ•จ์ˆ˜ ํ˜ธ์ถœ ์—ฌ๋ถ€ ํ™•์ธ
  expect(sumSpy).toHaveBeenCalled()
  expect(sumSpy).toHaveBeenCalledTimes(1)

  // ํ˜ธ์ถœ๋œ ์ธ์ˆ˜ ํ™•์ธ
  expect(sumSpy).toHaveBeenCalledWith(3, 7)

  // ๊ฒฐ๊ณผ๊ฐ’ ํ™•์ธ
  expect(result).toBe(10)

  // Spy ํ•ด์ œ
  // ํ•ด์ œ ํ•จ์œผ๋กœ์„œ ์›๋ž˜ ํ•จ์ˆ˜์˜ ๋™์ž‘์„ ๋ณต์›ํ•˜์—ฌ, ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
  sumSpy.mockRestore()
})

๐Ÿ—‚๏ธ์ฐธ๊ณ  ๋„์„œ

  • ํ”„๋ŸฐํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ์ž…๋ฌธ
This post is licensed under CC BY 4.0 by the author.