Skip to content

tintoll/javascript_test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

15 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

javascript ํ…Œ์ŠคํŒ…

  • javascript์—์„œ ํ…Œ์ŠคํŒ… ๋„๊ตฌ์€ ์ •๋ง ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” jest๋ฅผ ์‚ฌ์šฉํ•ด ๋ณผ ๊ฒƒ์ด๋‹ค.

jest ๋ฌธ๋ฒ•

// sum.js
function sum(a, b) {
  return a + b;
}

function sumOf(numbers) {
  // let result = 0;
  // numbers.forEach(n => {
  //   result += n;
  // });
  // return result;
  
  // reduce๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐฐ์—ด์˜ ์ดํ•ฉ์„ ๊ตฌํ•œ๋‹ค. 
  return numbers.reduce((acc, current) => acc +  current, 0);
}
// ๊ฐ๊ฐ ๋‚ด๋ณด๋‚ด๊ธฐ
exports.sum = sum;
exports.sumOf = sumOf;
// sum.test.js
describe('sum', () => {
  it('calculates 1 + 2', () => {
    expect(sum(1, 2)).toBe(3);
  });

  it('calculates all numbers', () => {
    const array = [1,2,3,4,5];
    expect(sumOf(array)).toBe(15);
  });
});
it, test
  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋งŒ๋“ค๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜
describe
  • ์—ฌ๋Ÿฌ๊ฐœ์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๋ฌถ์–ด ์ค„๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜
expect
  • ํŠน์ •๊ฐ’์ด ~~์ผ ๊ฒƒ์ด๋‹ค ๋ผ๊ณ  ์‚ฌ์ „์— ์ •์˜ํ•˜๊ณ  ํ†ต๊ณผํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณต์‹œํ‚ค๊ณ  ํ†ต๊ณผํ•˜์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํŒจ์‹œํ‚ต๋‹ˆ๋‹ค.
toBe
  • ํŠน์ •๊ฐ’๊ณผ ์šฐ๋ฆฌ๊ฐ€ ์ •ํ•œ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ์„ ํ•ด์ค๋‹ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ž‘์„ฑ์‹œ ์ด์ 
  • ๋ฆฌํŒฉํ† ๋ง ์ดํ›„ ์ฝ”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋Š”๊ฒƒ์„ ๊ฒ€์ฆํ•˜๊ธฐ ๋งค์šฐ ๊ฐ„ํŽธํ•˜๋‹ค.

React ํ…Œ์ŠคํŠธ

  • react-dom/test-utils ์•ˆ์— ๋“ค์–ด ์žˆ๋Š” ์œ ํ‹ธ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ๋„ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ• ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณต์žกํ•˜๊ณ  ๋ถˆํŽธํ•œ ๋ถ€๋ถ„๋“ค์ด ์žˆ์–ด์„œ React ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ ํ…Œ์ŠคํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค.

Enzyme ๊ณผ react-testing-library

  • React ๊ณต์‹ ๋ฌธ์„œ์—์„œ ๊ถŒ์žฅํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” react-testing-library ์ด๋‹ค. ํ˜„์žฌ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” Enzyme์˜ ๋Œ€์ฒด ๋ฐฉ์•ˆ์ด๋ผ๊ณ  ์–ธ๊ธ‰ํ•˜๊ณ  ์žˆ๋‹ค.
  • Enzyme์€ Airbnb์—์„œ ๋งŒ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ• ๋•Œ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ ๊ธฐ๋Šฅ์„ ์ž์ฃผ ์ ‘๊ทผํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” props, state๋ฅผ ํ™•์ธํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ์˜ ๋‚ด์žฅ ๋ฉ”์†Œ๋“œ๋ฅผ ์ง์ ‘ํ˜ธ์ถœํ•˜๊ธฐ๋„ ํ•œ๋‹ค.
  • react-testing-library๋Š” ๋ Œ๋”๋ง ๊ฒฐ๊ณผ์— ์ง‘์ค‘ํ•œ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด์„œ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  ์‹ค์ œ DOM์— ๋Œ€ํ•˜์—ฌ ์‹ ๊ฒฝ์„ ๋งŽ์ด ์“ฐ๊ณ , ์‹ค์ œ ํ™”๋ฉด์— ๋ฌด์—‡์ด ๋ณด์—ฌ์ง€๋Š”์ง€, ์–ด๋– ํ•œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„๋•Œ ํ™”๋ฉด์— ์›ํ•˜๋Š” ๋ณ€ํ™”๊ฐ€ ์ƒ๊ฒผ๋Š”์ง€ ์ด๋Ÿฐ ๊ฒƒ์„ ํ™•์ธํ•˜๊ธฐ์— ์กฐ๊ธˆ ๋” ์ตœ์ ํ™” ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ํ…Œ์ŠคํŠธํ•˜๋Š”๊ฒƒ์ด ๋” ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.
  • 2๊ฐœ๋ฅผ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Enzyme ํ…Œ์ŠคํŠธ

์„ค์น˜

$ npm i enzyme enzyme-adapter-react-16

configuration

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŒ…

  • ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŒ…์ด๋ž€, ๋ Œ๋”๋ง๋œ ๊ฒฐ๊ณผ๊ฐ€ ์ด์ „์— ๋ Œ๋”๋งํ•œ ๊ฒฐ๊ณผ์™€ ์ผ์น˜ํ•˜๋Š” ํ™•์ธํ•˜๋Š” ์ž‘์—…
์„ค์น˜
$ npm i enzyme-to-json
``

##### ์Šค๋ƒ…์ƒท ์„ค์ •
โ€‹```javascript
// package.json ์— ์•„๋ž˜ ๋‚ด์šฉ ์ถ”๊ฐ€ 

"jest": {
  "snapshotSerializers": ["enzyme-to-json/serializer"]
}
๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ
import React from "react";
import { mount } from "enzyme";
import Profile from './Profile';

describe('<Profile />', () => {

  it('matches snapshot', () => {
    // mountํ•จ์ˆ˜๋Š” Enzyme ์„ ํ†ตํ•˜์—ฌ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•ด์ค๋‹ˆ๋‹ค.
    const wrapper = mount(<Profile username="tintoll" name="ํ™”๋‹ˆ" />);
    expect(wrapper).toMatchSnapshot();
  });

  it('renders username and name', () => {
    const wrapper = mount(<Profile username="tintoll" name="ํ™”๋‹ˆ" />);
    
    expect(wrapper.props().username).toBe('tintoll');
    expect(wrapper.props().name).toBe('ํ™”๋‹ˆ');

    // find ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • DOM์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค. querySelector์™€ ๋˜‘๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    const boldElement = wrapper.find('b');
    expect(boldElement.contains('tintoll')).toBe(true);

    const spanElement = wrapper.find('span');
    expect(spanElement.text()).toBe('(ํ™”๋‹ˆ)');
  })
});
ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ ํ…Œ์ŠคํŠธ
import React from 'react';
import { shallow } from 'enzyme';
import Counter from './Counter';

describe('<Counter />', () => {

  it('matches snapshot', () => {
    // shallow๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ๋˜ ๋‹ค๋ฅธ ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ด๋ฅผ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    // mount๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€ ์ปดํฌ๋„ˆํŠธ๊นŒ์ง€ ๋ Œ๋”๋ง ๋œ๋‹ค.
    const wrapper = shallow(<Counter />);
    expect(wrapper).toMatchSnapshot();
  });

  it('has initial number', () => {
    const wrapper = shallow(<Counter />);
    // ์ปดํฌ๋„ŒํŠธ state๋ฅผ ์กฐํšŒํ• ๋•Œ state()ํ•จ์ˆ˜๋ฅผ ์ด์šฉ
    expect(wrapper.state().number).toBe(0);
  });

  it('increases', () => {
    const wrapper = shallow(<Counter />);
    // ๋‚ด์žฅ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ• ๋•Œ instance()ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์กฐํšŒํ›„ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
    wrapper.instance().handleIncrease();
    expect(wrapper.state().number).toBe(1);
  });

  it('decreases', () => {
    const wrapper = shallow(<Counter />);
    wrapper.instance().handleDecrease();
    expect(wrapper.state().number).toBe(-1);
  });

  it('calls handleIncrease', () => {
    // ํด๋ฆญ์ด๋ฒคํŠธ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ดํŠธ ํ•˜๊ณ  state๋ฅผ ํ™•์ธ 
    const wrapper = shallow(<Counter />);
    const plusButton = wrapper.findWhere(
      node => node.type() === 'button' && node.text() === '+1'
    );
    plusButton.simulate('click');
    expect(wrapper.state().number).toBe(1);
  });

  it('calls handleDecrease', () => {
    // ํด๋ฆญ์ด๋ฒคํŠธ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ดํŠธ ํ•˜๊ณ  h2ํƒœ์˜ ํ…์Šค๋ฅผ ํ™•์ธ
    const wrapper = shallow(<Counter />);
    // findWhere ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›ํ•˜๋Š” ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ํƒœ๊ทธ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.
    const minuxButton = wrapper.findWhere(
      node => node.type() === 'button' && node.text() === '-1'
    );
    // ์ด๋ฒคํŠธ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ดํŠธ ํ• ๋•Œ์—๋Š” ์›ํ•˜๋Š” ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ฐพ์•„์„œ simulate()ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    // ์ฒซ๋ฒˆ์งธ๋Š” ์ด๋ฒคํŠธ์ด๋ฆ„, ๋‘๋ฒˆ์งธ๋Š” ์ด๋ฒคํŠธ ๊ฐ์ฒด๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค.
    /*
      // input์˜ change์ด๋ฒคํŠธ์ผ ๊ฒฝ์šฐ 
      input.simulate('change', {
        target: {
          value: 'hello world'
        }
      });
    */

    minuxButton.simulate('click');
    const number = wrapper.find('h2');
    expect(number.text()).toBe('-1');
  });

});
ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์™€ Hooksํ…Œ์ŠคํŠธ
import React from 'react';
import { mount } from 'enzyme';
import HookCounter from './HookCounter';


// ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ํด๋ž˜์Šคํ˜• ์ปดํฌ๋„ŒํŠธ์™€ ๋‹ฌ๋ฆฌ ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ ๋ฐ ์ƒํƒœ๋ฅผ ์กฐํšŒ ํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. 
// Hooks ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๊ผญ shallow ๊ฐ€ ์•„๋‹Œ mount ๋ฅผ ์‚ฌ์šฉํ•˜์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค. 
// ๊ทธ ์ด์œ ๋Š”, useEffect Hook ์€ shallow ์—์„œ ์ž‘๋™ํ•˜์ง€ ์•Š๊ณ , ๋ฒ„ํŠผ ์—˜๋ฆฌ๋จผํŠธ์— ์—ฐ๊ฒฐ๋˜์–ด์žˆ๋Š” ํ•จ์ˆ˜๊ฐ€ ์ด์ „ ํ•จ์ˆ˜๋ฅผ ๊ฐ€๋ฅดํ‚ค๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, 
// ์˜ˆ๋ฅผ ๋“ค์–ด +1 ๋ฒ„ํŠผ์˜ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๋‘๋ฒˆ ์‹œ๋ฎฌ๋ ˆ์ดํŠธํ•ด๋„ ๊ฒฐ๊ณผ๊ฐ’์ด 2๊ฐ€ ๋˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ 1์ด ๋ฉ๋‹ˆ๋‹ค.

describe('<HookCounter />', () => {

  it('matches snapshot', () => {
    const wrapper = mount(<HookCounter />);
    expect(wrapper).toMatchSnapshot();
  });

  it('increases', () => {
    const wrapper = mount(<HookCounter />);
    let plusButton = wrapper.findWhere(
      node => node.type() === 'button' && node.text() === '+1'
    );
    plusButton.simulate('click');
    plusButton.simulate('click');

    const number = wrapper.find('h2');

    expect(number.text()).toBe('2');
  });

  it('decreases', () => {
    const wrapper = mount(<HookCounter />);
    let decreaseButton = wrapper.findWhere(
      node => node.type() === 'button' && node.text() === '-1'
    );
    decreaseButton.simulate('click');
    decreaseButton.simulate('click');

    const number = wrapper.find('h2');

    expect(number.text()).toBe('-2');
  });

});

react-testing-library ํ…Œ์ŠคํŠธ

์„ค์น˜

# ์˜ˆ์ „๋ฒ„์ „
$ npm i react-testing-library jest-dom
# ์ตœ์‹ ๋ฒ„์ „
$ npm i @testing-library/react @testing-library/jest-dom

configuration(src/setupTests.js)

// ๋ฆฌ์•กํŠธ์—์„œ DOM ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์œ„ํ•œ [JSDOM]์ด๋ผ๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ document.body์— ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.
// cleanup-after-each๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋๋‚ ๋•Œ๋งˆ๋‹ค ๊ธฐ์กด์— ๊ฐ€์ƒ์˜ ํ™”๋ฉด์— ๋‚จ์•„์žˆ๋Š” UI๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
import '@testing-library/react/cleanup-after-each';

// jest์—์„œ DOM๊ด€๋ จ matcher๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
import '@testing-library/jest-dom/extend-expect';

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ

import React from 'react';
import { render } from "@testing-library/react";
import Profile from './Profile';

describe('<Profile />', () => {
  it('matches snapshot', () => {
    const utils = render(<Profile username="velopert" name="๊น€๋ฏผ์ค€" />);
    expect(utils.container).toMatchSnapshot();
  });

  it('shows the props correctly', () => {
    const utils = render(<Profile username="velopert" name="๊น€๋ฏผ์ค€" />);
    utils.getByText('velopert!'); //  velopert๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ง„ ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
    utils.getByText('(๊น€๋ฏผ์ค€)');//  (๊น€๋ฏผ์ค€)๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ง„ ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
    utils.getByText(/๊น€/); // ์ •๊ทœ์‹ /๊น€/ ์„ ํ†ต๊ณผํ•˜๋Š” ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ 
  });
});

๋‹ค์–‘ํ•œ ์ฟผ๋ฆฌ

render ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋‚˜๋ฉด ๊ทธ ๊ฒฐ๊ณผ๋ฌผ ์•ˆ์—๋Š” ๋‹ค์–‘ํ•œ ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋“ค์ด ์žˆ๋‹ค. ์ด ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋“ค์€ Variant ์™€ Queries ์˜ ์กฐํ•ฉ์œผ๋กœ ๋„ค์ด๋ฐ์ด ์ด๋ฃจ์ ธ ์žˆ๋‹ค. ๋„ˆ๋ฌด ๋งŽ์•„์„œ ์ถ”์ฒœํ•˜๋Š” ์ฟผ๋ฆฌ๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ ๋“ฏ ํ•˜๋‹ค.

  1. getByLabelText

    1. label ์ด ์žˆ๋Š” input ์˜ label ๋‚ด์šฉ์œผ๋กœ input ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

      <label for="username-input">์•„์ด๋””</label>
      <input id="username-input" />
      
      const inputNode = getByLabelText('์•„์ด๋””');
  2. getByPlaceholderText

    1. placeholder ๊ฐ’์œผ๋กœ input ๋ฐ textarea ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

      <input placeholder="์•„์ด๋””" />;
      
      const inputNode = getByPlaceholderText('์•„์ด๋””');
  3. getByText

    1. ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ…์ŠคํŠธ ๊ฐ’์œผ๋กœ DOM ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

      <div>Hello World!</div>;
      
      const div = getByText('Hello World!');
  4. getByDisplayValue

    1. input, textarea, select ๊ฐ€ ์ง€๋‹ˆ๊ณ  ์žˆ๋Š” ํ˜„์žฌ ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

      <input value="text" />;
      
      const input = getByDisplayValue('text');
  5. getByAltText

    1. alt ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์—˜๋ฆฌ๋จผํŠธ (์ฃผ๋กœ img) ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

      <img src="/awesome.png" alt="awesome image" />;
      
      const imgAwesome = getByAltText('awesomse image');
  6. getByTitle

    1. title ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” DOM ํ˜น์€ title ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ง€๋‹ˆ๊ณ ์žˆ๋Š” SVG ๋ฅผ ์„ ํƒ ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  7. getByRole

    1. ํŠน์ • role ๊ฐ’์„ ์ง€๋‹ˆ๊ณ  ์žˆ๋Š” ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
    <span role="button">์‚ญ์ œ</span>;
    
    const spanRemove = getByRole('button');
  8. getByTestId

    1. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ชป ์„ ํƒํ• ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ธ๋ฐ์š”, ํŠน์ • DOM ์— ์ง์ ‘ test ํ•  ๋•Œ ์‚ฌ์šฉํ•  id ๋ฅผ ๋‹ฌ์•„์„œ ์„ ํƒํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

      <div data-testid="commondiv">ํ”ํ•œ div</div>;
      
      const commonDiv = getByTestId('commondiv');

์ด๋ฒคํŠธ

fireEvent()ํ•จ์ˆ˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์ค๋‹ˆ๋‹ค. ์‚ฌ์šฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

fireEvent.์ด๋ฒคํŠธ์ด๋ฆ„(DOM, ์ด๋ฒคํŠธ๊ฐ์ฒด);
fireEvent.change(myInput, { target: { value: 'hello world' } });

jest.fn()

jest.fn()ํ•จ์ˆ˜๋Š” jest์—์„œ ์ œ๊ณตํ•˜๋Š” mockํ•จ์ˆ˜์ด๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ ๋‹ค์Œ toBeCalled, toBeCalledWith์™€ ๊ฐ™์€ matcher๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋๋Š”์ง€, ํ˜ธ์ถœ๋์œผ๋ฉด ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ํ˜ธ์ถœ๋๋Š”์ง€ ์‰ฝ๊ฒŒ ํ™•์ธ ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

it('calls onInsert and clears input', () => {
  const onInsert = jest.fn(); 
  const { input, button } = setup({ onInsert });
  // ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ํ•  ๋•Œ์—๋Š” ๋งˆ์น˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋œ ์ž…์žฅ์œผ๋กœ,์•„๋ž˜์™€ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.
  // ๊ธ€์ž์ž…๋ ฅํ•˜๊ณ   
  fireEvent.change(input, {
    target: {
      value: 'TDD ๋ฐฐ์šฐ๊ธฐ'
    }
  });
  // ๋“ฑ๋ก ๋ฒ„ํŠผํด๋ฆญ
  fireEvent.click(button);
  expect(onInsert).toBeCalledWith('TDD ๋ฐฐ์šฐ๊ธฐ'); // onInsert ๊ฐ€ 'TDD ๋ฐฐ์šฐ๊ธฐ' ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋์–ด์•ผํ•จ
  expect(input).toHaveAttribute('value',''); // input์ด ๋น„์›Œ์ ธ์•ผํ•จ.
})

toHaveStyle()

  • toHaveStyle ์ด๋ผ๋Š” matcher ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น DOM ์— ํŠน์ • ์Šคํƒ€์ผ์ด ์žˆ๋Š”์ง€ ์‰ฝ๊ฒŒ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • not ์ด๋ผ๋Š” ํ‚ค์›Œ๋“œ๋Š” ํŠน์ • ์กฐ๊ฑด์ด ๋งŒ์กฑํ•˜์ง€ ์•Š์•„์•ผ ํ•จ์„ ์˜๋ฏธ
it('shows line-through on span when done is false', () => {
  const { span } = setup({ todo: { ...sampleTodo, done: false } });
  expect(span).not.toHaveStyle('text-decoration: line-through;');
});

toBeInTheDocument()

  • toBeInTheDocument ์ด๋ผ๋Š” matcher ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์กŒ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
expect(todoText).not.toBeInTheDocument();

// ๋‹ค๋ฅธ toBeInTheDocumentํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ‘œํ˜„ํ•˜๊ธฐ 
const removedText = queryByText('TDD ๋ฐฐ์šฐ๊ธฐ');
expect(removedText).toBeNull();

๋น„๋™๊ธฐ ์ž‘์—… ํ…Œ์ŠคํŠธ

  • ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” react-testing-library์—์„œ ์ง€์›ํ•˜๋Š” Async Utilitiesํ•จ์ˆ˜๋“ค์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

Async Utilities ํ•จ์ˆ˜๋“ค

wait
  • ํŠน์ • ์ฝœ๋ฐฑ์—์„œ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ๋•Œ ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋Š” ํ•จ์ˆ˜
it('reveals text wheen toggle is ON', async () => {
  const { getByText } = render(<DelayedToggle />);
  const toggleButton = getByText('ํ† ๊ธ€');
  fireEvent.click(toggleButton);
  // ์ฝœ๋ฐฑ ์•ˆ์˜ ํ•จ์ˆ˜๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š์„๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
  // timeout ๊ธฐ๋ณธ๊ฐ’์€ 4500ms ์ด๋‹ค. 
  await wait( () => getByText('์•ผํ˜ธ!!'), {timeout : 3000}); 

});
waitForElement
  • ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ๊ฐ€, ๋‚˜ํƒ€๋‚ฌ๊ฑฐ๋‚˜, ๋ฐ”๋€Œ์—ˆ๊ฑฐ๋‚˜, ์‚ฌ๋ผ์งˆ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋๋‚  ๋•Œ ์šฐ๋ฆฌ๊ฐ€ ์„ ํƒํ•œ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ resolve ํ•ฉ๋‹ˆ๋‹ค.
it('toggles text ON/OFF', async () => {
  const { getByText } = render(<DelayedToggle />);
  const toggleButton = getByText('ํ† ๊ธ€');
  fireEvent.click(toggleButton);
  // waitForElement ํ•จ์ˆ˜๋Š” ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ๊ฐ€, ๋‚˜ํƒ€๋‚ฌ๊ฑฐ๋‚˜, ๋ฐ”๋€Œ์—ˆ๊ฑฐ๋‚˜, ์‚ฌ๋ผ์งˆ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค. 
  // ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ๋๋‚ ๋•Œ ์šฐ๋ฆฌ๊ฐ€ ์„ ํƒํ•œ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ resolveํ•œ๋‹ค.
  const text = await waitForElement(() => getByText('ON'));
  expect(text).toHaveTextContent('ON');
});
waitForDomChange
  • ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฒ€์‚ฌํ•˜๊ณ  ์‹ถ์€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ํ•ด๋‹น ์—˜๋ฆฌ๋จผํŠธ์—์„œ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒ ํ•  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. ํ”„๋กœ๋ฏธ์Šค๊ฐ€ resolve ๋์„๋• mutationList๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ DOM์ด ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์•Œ์ˆ˜ ์žˆ๋‹ค.
it('changes something when button is clicked', async () => {
  const { getByText, container } = render(<DelayedToggle />);
  const toggleButton = getByText('ํ† ๊ธ€');
  fireEvent.click(toggleButton);
  // waitForDomChange๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฒ€์‚ฌํ•˜๊ณ  ์‹ถ์€ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ํ•ด๋‹น ์—˜๋ฆฌ๋จผํŠธ์—์„œ ๋ณ€ํ™”๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์ค๋‹ˆ๋‹ค.
  const mutations = await waitForDomChange({container});
  // ํ”„๋กœ๋ฏธ์Šค๊ฐ€ resolve๋์„๋•Œ mutationList๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ DOM์ด ์–ด๋–ป๊ฒŒ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์•Œ์ˆ˜ ์žˆ๋‹ค.
  // console.log(mutations);
});
waitForElementToBeRemoved
  • ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์งˆ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
t('removes text when toggle is OFF', async () => {
  const { getByText, container } = render(<DelayedToggle />);
  const toggleButton = getByText('ํ† ๊ธ€');
  fireEvent.click(toggleButton);

  await waitForDomChange({container}); //  ON์ด ๋จ
  getByText('์•ผํ˜ธ!!');
  fireEvent.click(toggleButton);
  // waitForElementToBeRemove๋Š” ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์งˆ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  await waitForElementToBeRemoved( () => getByText('์•ผํ˜ธ!!'));

});

REST-API ํ˜ธ์ถœ ํ…Œ์ŠคํŠธ

  • Rest API๋ฅผ ํ˜ธ์ถœํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์ด๋ฅผ mocking ํ•ฉ๋‹ˆ๋‹ค.
axios-mock-adapter ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ
  • MockAdapter ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • API ์š”์ฒญ์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์–ด๋–ค ์‘๋‹ต์ด ์™€์•ผ ํ•˜๋Š”์ง€ ์ง์ ‘ ์ •์˜ํ•ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • delayResponse ์˜ต์…˜์„ ์„ค์ •ํ•˜๋ฉด ๋”œ๋ ˆ์ด๋ฅผ ์ž„์˜์ ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„ค์ •์€ ์—†์–ด๋„ ์ƒ๊ด€ ์—†์Šต๋‹ˆ๋‹ค.
describe('<UserProfile />', () => {
  const mock = new MockAdapter(axios, { delayResponse : 200}) // 200ms ๊ฐ€์งœ ๋”œ๋ ˆ์ด ์„ค์ •
  // API ์š”์ฒญ์— ๋Œ€ํ•˜์—ฌ ์‘๋‹ต ๋ฏธ๋ฆฌ ์ •ํ•˜๊ธฐ 
  mock.onGet('https://jsonplaceholder.typicode.com/users/1').reply(200, {
    id: 1,
    name: 'Leanne Graham',
    username: 'Bret',
    email: 'Sincere@april.biz',
    address: {
      street: 'Kulas Light',
      suite: 'Apt. 556',
      city: 'Gwenborough',
      zipcode: '92998-3874',
      geo: {
        lat: '-37.3159',
        lng: '81.1496'
      }
    },
    phone: '1-770-736-8031 x56442',
    website: 'hildegard.org',
    company: {
      name: 'Romaguera-Crona',
      catchPhrase: 'Multi-layered client-server neural-net',
      bs: 'harness real-time e-markets'
    }
  });


  it('calls getUser API loads userData properly', async () => {
    const { getByText } = render(<UserProfile id={1} />);
    await waitForElement(() => getByText('๋กœ๋”ฉ์ค‘...')); // ๋กœ๋”ฉ์ค‘.. ๋ฌธ๊ตฌ ๋ณด์—ฌ์ค˜์•ผํ•จ
    await waitForElement(() => getByText('Bret')); // Bret (username) ์„ ๋ณด์—ฌ์ค˜์•ผํ•จ
  });
});
axios-mock-adapter ํ™œ์šฉ
ํ•œ๋ฒˆ์— mockingํ•˜๊ธฐ - replyOnce
mock.onGet('/users').replyOnce(200, users);

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์š”์ฒญ์„ ๋”ฑ ํ•œ๋ฒˆ๋งŒ mocking ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ๋ฒˆ ์š”์ฒญ์„ ํ•˜๊ณ  ๋‚˜๋ฉด ๊ทธ ๋‹ค์Œ ์š”์ฒญ์€ ์ •์ƒ์ ์œผ๋กœ ์š”์ฒญ์ด ๋ฉ๋‹ˆ๋‹ค.

replyOnce ๋ฅผ ์—ฐ๋‹ฌ์•„์„œ ์‚ฌ์šฉํ•˜๊ธฐ
mock
  .onGet('/users')
  .replyOnce(200, users) // ์ฒซ๋ฒˆ์งธ ์š”์ฒญ
  .onGet('/users')
  .replyOnce(500); // ๋‘๋ฒˆ์งธ ์š”์ฒญ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฒซ๋ฒˆ์งธ ์š”์ฒญ๊ณผ ๋‘๋ฒˆ์งธ ์š”์ฒญ์„ ์—ฐ๋‹ฌ์•„์„œ ์„ค์ • ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์—ฌ๋Ÿฌ๋ฒˆ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์ด๋Ÿฐ ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์•„๋ฌด ์š”์ฒญ์ด๋‚˜ mocking ํ•˜๊ธฐ - onAny()

๋ณดํ†ต ๋ฉ”์„œ๋“œ์— ๋”ฐ๋ผ onGet(), onPost() ์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ์š”, onAny() ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ค ๋ฉ”์„œ๋“œ๋˜ mocking ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

mock.onAny('/foo').reply(200);
// ์ฃผ์†Œ๋ฅผ ์ƒ๋žตํ•˜๋ฉด ์–ด๋–ค ์ฃผ์†Œ๋“  mocking ํ•ฉ๋‹ˆ๋‹ค.
mock.onAny().reply(200);
reset ๊ณผ restore

reset์€ mock ์ธ์Šคํ„ด์Šค์— ๋“ฑ๋ก๋œ ๋ชจ๋“  mock ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ์— ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ณ„๋กœ ๋‹ค๋ฅธ mock ์„ค์ •์„ ํ•˜๊ณ  ์‹ถ์œผ์‹œ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

mock.reset();

restore์€ axios ์—์„œ mocking ๊ธฐ๋Šฅ์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ์— ์‹ค์ œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋‹ค๊ฐ€ ์š”์ฒญ์ด ์‹ค์ œ๋กœ ๋‚ ๋ผ๊ฐ€๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

mock.restore();

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published