Post

Jest - clipboard test

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

๊ฐœ์ธ ํ”„๋กœ์ ํŠธ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์—์„œ ์˜ค๋žœ ์‹œ๊ฐ„ ์ง„ํ–‰์ด ์•ˆ๋˜์—ˆ๋˜ ๋ถ€๋ถ„์ด ์žˆ์—ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ share-button์„ ํด๋ฆญํ•˜๋ฉด clipboard์— ์›ํ•˜๋Š” url์ด ์ œ๋Œ€๋กœ ๋ณต์‚ฌ๊ฐ€ ๋˜๋Š”์ง€์— ๋Œ€ํ•œ ๋ฌธ์ œ์˜€๋Š”๋ฐ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ, ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ์ž์ฒด๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹คโ€ฆ ๐Ÿ˜ฅ

โœ…์‹ค์ œ UI ํ๋ฆ„

๋‚ด๊ฐ€ ํ…Œ์ŠคํŠธํ•˜๊ณ ์ž ํ•˜๋Š” UI์˜ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์‚ฌ์šฉ์ž๊ฐ€ ImageCard๋ฅผ ํด๋ฆญํ•˜๋ฉด zustand๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹น ImageCard data๋ฅผ Modal ์ปดํฌ๋„ŒํŠธ๋กœ ๋„˜๊ฒจ์ค€๋‹ค.
  2. Modal Component๊ฐ€ ๋ Œ๋”๋ง ๋˜๋ฉฐ share-button์„ ์ œ๊ณตํ•œ๋‹ค.
  3. ์‚ฌ์šฉ์ž๊ฐ€ share-button์„ ํด๋ฆญํ•˜๋ฉด, ๋ณต์‚ฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” alert์„ ์ œ๊ณตํ•œ๋‹ค.
  4. ์‚ฌ์šฉ์ž์˜ clipboard์— ์ด๋ฏธ์ง€ url์ด ๋ณต์‚ฌ๋œ๋‹ค.

1๋ฒˆ์˜ zustand ๊ด€๋ จ ํ…Œ์ŠคํŠธ์˜ ๊ฒฝ์šฐ ์ด๋ฏธ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ฑฐ์ณ ์™„๋ฃŒ๋œ ๋ถ€๋ถ„์ด์–ด์„œ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์•˜์œผ๋‚˜, 3๋ฒˆ 4๋ฒˆ ํ๋ฆ„์—์„œ ๊ณ„์†ํ•ด์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

โœ…1์ฐจ ์‹œ๋„

1์ฐจ ์‹œ๋„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. global ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ navigator๋ฅผ mock ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜โ€ฆ clipboard๊ฐ€ readOnly ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆ๊ฐ€ํ•˜๋‹ค๋Š” ๋ฉ”์„ธ์ง€๊ฐ€ ๋‚˜์™”๋‹ค.

Cannot assign to โ€˜clipboardโ€™ because it is a read-only property.ts(2540)

1
2
3
 global.navigator.clipboard = {
    writeText: jest.fn().mockResolvedValue(),
  };

โœ…2์ฐจ ์‹œ๋„

๋‚˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๊ฒช์—ˆ๊ณ , ํ•ด๊ฒฐํ–ˆ๋‹ค๋Š” ๋ธ”๋กœ๊ทธ ๊ธ€์„ ๋ณด๊ณ  spyOn์„ ๋„์ž…ํ•ด๋ณด๊ณ ์ž ํ–ˆ์œผ๋‚˜โ€ฆ ์ด ๋˜ํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ,

1
const writeTextMock = jest.spyOn(navigator.clipboard, 'writeText').mockResolvedValue();

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ์œผ๋‚˜

1
expect(writeTextMock).toHaveBeenCalledWith(mockImageData[0].largeImageURL);

toHaveBeenCalledWith ๋ถ€๋ถ„์—์„œ Number of calls: 0 ๋ผ๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. writeTextMock์ด ์ œ๋Œ€๋กœ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๋ฌธ์ œ์˜€๋Š”๋ฐโ€ฆ

โœ…3์ฐจ ์‹œ๋„

ํ˜น์‹œ ์œ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๊ฐ€, click event ์ฒ˜๋ฆฌ ์‹œ์ ๊ณผ ๊ด€๋ จ๋œ ๋ฌธ์ œ์ผ๊นŒ ํ•ด์„œ waitFor๋กœ ๊ฐ์‹ธ ์‹คํ–‰ํ•ด๋ณด์•˜๊ณ , ์ด๋ฒˆ์—” alert์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ„๋‹จํ•˜๊ฒŒ mock ์ฒ˜๋ฆฌ ํ•ด์ฃผ์—ˆ๋‹ค.

1
2
3
4
5
6
window.alert = jest.fn();

//waitFor๋กœ ๊ฐ์‹ธ ์‹œ๋„ํ•œ ์ฝ”๋“œ
await waitFor(() => {
  expect(writeTextMock).toHaveBeenCalledWith(mockImageData[0].largeImageURL);
});

โœ…์ตœ์ข…

์—ฌ๋Ÿฌ ์‹œ๋„ ๋์— ์ตœ์ข…์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๋ธ”๋กœ๊ทธ์—์„œ ๋ณด์•˜๋˜, ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์ „์—, Jest ํ™˜๊ฒฝ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์กด์žฌํ•˜์ง€ ์•Š๋Š” navigator.clipboard๋ฅผ Object.defineProperty๋ฅผ ํ†ตํ•ด ์ •์˜ํ•˜๊ณ  spyOn์„ ํ†ตํ•ด ์ด๋ฏธ ์ •์˜๋œ navigator.clipboard.writeText์˜ ํ˜ธ์ถœ ํšŸ์ˆ˜ ๋“ฑ์„ ๊ฐ์‹œํ•˜๋„๋ก ํ•œ ๊ฒƒ์ด๋‹ค.

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
 it("์‚ฌ์šฉ์ž๊ฐ€ ๊ณต์œ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น URL์ด ๋ณต์‚ฌ๋œ๋‹ค.", async () => {
    // alert๋ฅผ mock ์ฒ˜๋ฆฌ ํ•œ๋‹ค.
    window.alert = jest.fn();

    // global.navigator ๊ฐ์ฒด์— clipboard ์†์„ฑ์„ ์ •์˜ํ•œ๋‹ค.
    Object.defineProperty(global.navigator, "clipboard", {
      value: {
        writeText: jest.fn(),
      },
      writable: true,
    });

    const writeTextMock = jest
      .spyOn(navigator.clipboard, "writeText")
      .mockResolvedValue();

    // mock ๋ฐ์ดํ„ฐ ๋ฐ store ์„ค์ •
    mockModalImageIdStore({ modalImage: mockImageData[0] });

    // Modal ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง
    const { getByTestId } = render(<Modal />);

    const shareButton = getByTestId("share-button");

    // ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
    fireEvent.click(shareButton); 

    // writeTextMock ํ˜ธ์ถœ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.
    await waitFor(() => {
      expect(writeTextMock).toHaveBeenCalledWith(mockImageData[0].largeImageURL);
    });
  });

์ตœ์ข…์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ž‘๋™ํ•ด PASS ๋˜์—ˆ๋‹ค.

๐Ÿ™‹โ€โ™€๏ธ ๋งˆ๋ฌด๋ฆฌ

๋ธ”๋กœ๊ทธ ๊ธ€์€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ ์—ˆ์ง€๋งŒ ์ด clipboard ํ…Œ์ŠคํŠธ์— ๊ฝค ์• ๋ฅผ ๋จน์—ˆ๋‹ค. ๐Ÿ˜ฅ ์ด๋Ÿฌ๋ฉด ํ…Œ์ŠคํŠธ ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ๋งŽ์ด ์†Œ์š”๋˜๋Š”๊ฑด ์•„๋‹๊นŒ ์œ„์ถ•๋˜๊ธฐ๋„ ํ–ˆ๊ณ โ€ฆ

ํ•˜์ง€๋งŒ ์–ด๋–ป๊ฒŒ๋“  ํ•ด๋ƒˆ๊ธฐ์— ๋จผ์ € ์Šค์Šค๋กœ ๋ฐ•์ˆ˜๐Ÿค—(ใ…Žใ…Ž)๋ฅผ ๋ณด๋‚ด๋ณธ๋‹ค.

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ผ๋ถ€๋Ÿฌ ์‹ค์ œ๋กœ ํ™”๋ฉด์— ํ•„์š”์—†๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด story๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ๋„ ํ•ด์˜, state๋ฅผ ์‚ฌ์šฉํ•ด๋„ ์ƒ๊ด€์—†๋Š” ๋กœ์ง์— zustand๋ฅผ ์จ์„œ ๋ Œ๋”๋ง ํ•ด ๋ณด๋Š” ๋“ฑ ๋” ์ƒ๊ฐํ•ด๋ณด๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์„ ๋งŒ๋“ค์—ˆ๋‹ค.

์ง์ ‘ ์ž‘์„ฑํ•ด๋ณด๋Š” ๊ณผ์ •์—์„œ ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๊ณ , ์ง€๊ธˆ๋„ ์—ฌ์ „ํžˆ ๋ฐฐ์šฐ๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์‹ค๋ฌด์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ทธ๋‚ ๊นŒ์ง€ ํž˜๋‚ด๋ณด์ž ํŒŒ์ดํŒ…!

๐Ÿ—‚๏ธ์ฐธ๊ณ  ์‚ฌ์ดํŠธ

This post is licensed under CC BY 4.0 by the author.