๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Vue

๐Ÿ‘Š vue jest study๊ธฐ๋ก

by frontChoi 2023. 3. 3.
๋ฐ˜์‘ํ˜•

๐Ÿฆถ ํŒจํ‚ค์ง€ ์„ค์น˜

node : 16.8.1
npm : 8.19.2

 ๐ŸคŸ vue jest ์„ค์น˜ 

npm install @vue/test-utils --save-dev
npm install @babel/preset-env --save-dev
npm install babel-core --save-dev
npm install babel-jest
npm install jest --save-dev
npm install --save-dev jest-environment-jsdom
npm install --save-dev jest-transform-stub
npm i @vue/vue3-jest --save-dev
 
 

๐Ÿคž jest.config.js

jest.config.js๋Š” jest๋ฅผ ์œ„ํ•œ ํ™˜๊ฒฝ์„ค์ •์ด๋‹ค.

 

moduleFileExtensions : ํ…Œ์ŠคํŠธํ•  ํ™•์žฅ์ž๋ฅผ ์„ค์ •ํ•˜๋ฉฐ, ๋ฐฐ์—ด๋กœ ์„ ์–ธ ๊ฐ€๋Šฅ

transform : vue,jsx,์ด๋ฏธ์ง€๋“ฑ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ

moduleNameMapper : alias๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ

testMatch : ์–ด๋–ค ํŒŒ์ผ์„ ํ…Œ์ŠคํŠธ ํ•  ๊ฒƒ์ธ์ง€ 

testPathIgnorePatterns : ํ…Œ์ŠคํŠธ๋ฅผ ๋ฌด์‹œํ•  ํด๋”

module.exports = {
    //ํ…Œ์ŠคํŠธ๋ฅผ ์‹ค์‹œํ•  ํ™•์žฅ์ž๋“ค์„ ์„ค์ •
    moduleFileExtensions: [
        "js",
        "json",
        "vue",
    ],
    //jest์—์„œ๋Š” javascript๋งŒ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ vue,์ด๋ฏธ์ง€๋“ฑ์„ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ํ•„์š”
    transform: {
        // `vue-jest`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  vue ํŒŒ์ผ(`*.vue`)์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
        ".*\\.(vue)$": "@vue/vue3-jest",
        // `babel-jest`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  js ํŒŒ์ผ(`*.js`)์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
        ".*\\.(js)$": "babel-jest",
        // ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ
        ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
     },
     moduleNameMapper: {
      // ๋ณ„์นญ @(ํ”„๋กœ์ ํŠธ/src) ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์œ„ ๊ฒฝ๋กœ์˜ ํŒŒ์ผ์„ ๋งตํ•‘ํ•ฉ๋‹ˆ๋‹ค
      "^@/(.*)$": "<rootDir>/src/$1",
    },
    testMatch: ["**/*.spec.[jt]s?(x)", "**/*.test.[jt]s?(x)"],
    // node_modules ๊ฒฝ๋กœ ํ•˜์œ„์— ์žˆ๋Š” ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ๋Œ€์ƒ์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค
    testPathIgnorePatterns: ["/node_modules/"],
    testEnvironmentOptions: {
      customExportConditions: ['node', 'node-addons'],
    },
    testEnvironment: "jsdom",
    bail: 1,
    verbose: true,
}

 

๐Ÿซต jest ์‚ฌ์šฉ๋ฒ•

describe ํ•จ์ˆ˜ : ๊ฐ€์žฅ ํฐ ๋ฒ”์œ„์˜ ํ•จ์ˆ˜์ด๋ฉด ์—ฐ๊ด€๋œ ๊ฒƒ๋“ค์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด์ฃผ๋Š” ๊ฒƒ.
test ํ•จ์ˆ˜ : ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์˜ ์‹œ์ž‘ ํ•จ์ˆ˜
describe("app.vue test", () => {
  test("should ", async () => {
    expect(1).toBe(1);
  });
});

 

๐ŸคŸ vue shallMount,mount

shallMount : ์ปดํผ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค. ๋‹ค๋งŒ ์ž์‹์ปดํผ๋„ŒํŠธ๊นŒ์ง€๋Š” ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š๋Š”๋‹ค.

mount : ์ปดํผ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋ง ํ•œ๋‹ค. ์ž์‹ ์ปดํผ๋„ŒํŠธ๊นŒ์ง€ ๋ Œ๋”๋ง ํ•œ๋‹ค.

import { shallowMount } from '@vue/test-utils';
import Helloworld from '@/components/HelloWorld.vue';

let wrapper = null;

beforeEach(() => {
  wrapper = shallowMount(Helloworld);
});

describe('HelloWorld.vue testing', () => {
  test('shallowMount helloword vue', async () => {});
});

 

 

๐Ÿค™ find ํ•จ์ˆ˜

component์ค‘์— find๋ฅผ ์ด์šฉํ•˜์—ฌ class,id๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

์•„๋ž˜๋Š” find๋ฅผ ์ด์šฉํ•˜์—ฌ ํ•ด๋‹น class์˜ text๋ฅผ ๋น„๊ตํ•˜๋Š” ์˜ˆ์ œ์ด๋‹ค.

import ChildComponent from '@/components/ChildComponent.vue';
import { shallowMount } from '@vue/test-utils';

let wrapper = null;
beforeEach(() => {
  wrapper = shallowMount(ChildComponent);
});

describe('ChildComponent Testing', () => {
  test('article ์กด์žฌ ์—ฌ๋ถ€', () => {
    // ์กด์žฌ์œ ๋ฌด ํ™•์ธ
    expect(wrapper.find('.child-desc').text()).toBe('์ž์‹ article');
  });
});

".child-desc"์˜ ํด๋ž˜์Šค๋ฅผ ์ฐพ์•„์„œ ".child-desc"์˜ ํ…์ŠคํŠธ๋ฅผ ๋น„๊ตํ•œ๋‹ค.

 

๐Ÿ‘† props ์„ค์ •ํ•˜๊ธฐ

์ž์‹ ์ปดํผ๋„ŒํŠธ์— jest๋ฅผ ์ด์šฉํ•˜์—ฌ props๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

import ListComponent from '@/components/ListComponent.vue';
import { shallowMount } from '@vue/test-utils';

let wrapper = null;

const mockData = [
  {
    Idx: 1,
    ToDoItem: 'css ๊ณต๋ถ€',
    createdAt: '2023-02-01T15:00:00.000Z',
    updatedAt: '2023-02-01T15:00:00.000Z',
  },
  {
    Idx: 2,
    ToDoItem: 'Java ๊ณต๋ถ€',
    createdAt: '2023-02-01T15:00:00.000Z',
    updatedAt: '2023-02-01T15:00:00.000Z',
  },
];

beforeEach(() => {
  wrapper = shallowMount(ListComponent);
});

describe('list component ', () => {
  // setProps๋ฅผ ํ†ตํ•ด props ์„ค์ •
  test('props ํ™•์ธ', async () => {
    await wrapper.setProps({
      list: mockData,
    });
    // list๊ฐ€ ์กด์žฌํ•  ๊ฒฝ์šฐ class="list"๋ฅผ ์ฐพ์•„์„œ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
    expect(wrapper.find('.list').exists()).toBeTruthy();
  });
});

 

๐Ÿ‘ฆ mocking

ํ•ด๋‹น ์ฝ”๋“œ์—์„œ ์˜์กดํ•˜๋Š” ๋ถ€๋ถ„์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ๋ถ€๋ถ„์„ mocking์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋‹จ์œ„ํ…Œ์ŠคํŠธ ์‹ค์‹œํ• ๋•Œ ์˜์กด์„ฑ์ด ์žˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๐Ÿ™ fn()

๊ฐ€์งœ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

describe('utils tests.js ', () => {
  test('func testing', () => {
    const mockFn = jest.fn();
    const returnValue = mockFn.mockReturnValue('I am a mock!');

    expect(returnValue()).toBe('I am a mock!');
  });
});

jest.fn()์„ ํ†ตํ•ด ๊ฐ€์งœ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ  mockReturnValue('I am a mock!')์„ ํ†ตํ•ด ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋ฅผ returnํ•ด์ค€๋‹ค.

๊ทธ๋ฆฌ๊ณ  return๋œ ๋ฐ์ดํ„ฐ๋ฅผ  expect(returnValue()).toBe('I am a mock!'); ์„ ํ†ตํ•ด return๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•œ๋‹ค.

 

 

๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์‹ถ์€๊ฒฝ์šฐ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

mockResolvedValue ๋ฅผ ํ†ตํ•ด์„œ 43์„ return ํ•ด์ฃผ๊ณ  ,  expect(result).toBe(43) ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๊ตํ•œ๋‹ค.

describe('utils tests.js ', () => {
  test('Asynchronous ', async () => {
    const mockFn = jest.fn().mockResolvedValue(43);
    const result = await mockFn(); // 43
    expect(result).toBe(43);
  });
});

๐ŸคŒ spyOn()

ํ•จ์ˆ˜๋ฅผ ๋‹จ์ˆœํžˆ ๋ช‡๋ฒˆ ํ˜ธ์ถœ๋๋Š”์ง€ ๊ฐ์‹œ์˜ ์—ญํ• ์ด๋‹ค.

๊ฐ„๋‹จํ•œ ์ธ์ž ๋‘๊ฐœ๋ฅผ ๋ฐ›์•„ ๋ง์…ˆ์„ ํ•˜๋Š” calc ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

//utils.js
const calc = (a, b) => {
  return a + b;
};

export { calc }

๊ทธ๋ฆฌ๊ณ  utils.test.js์—์„œ utils ์ „์ฒด๋ฅผ importํ•˜์—ฌ , calc ํ•จ์ˆ˜๋ฅผ spyOnํ•ด๋ณด์ž

import * as util from '../utils';

describe('utils tests.js ', () => {
   test('calc testing', async () => {
        const spyFn = jest.spyOn(util, 'calc');
        const result = util.calc(2, 3);
        //๋ช‡๋ฒˆ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
        expect(spyFn).toBeCalledTimes(1);
        //์–ด๋–ค๊ฐ’์„ ์ธ์ž์— ๋„ฃ์—ˆ๋Š”์ง€ ํ™•์ธ
        expect(spyFn).toBeCalledWith(2, 3);
        //ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ํ™•์ธ
        expect(result).toBe(5);
    });
})

toBeCalledTimes : spyOnํ•œ ํ•จ์ˆ˜๊ฐ€ ๋ช‡๋ฒˆ ํ˜ธ์ถœ๋๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

toBeCalledWith : ์–ด๋–ค๊ฐ’๊ณผ ํ•จ๊ป˜ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

 

๐Ÿ’ช ๊ฐ์ฒด ๋น„๊ต ํ•˜๊ธฐ

๊ฐ์ฒด๋ฅผ ๋น„๊ตํ• ๋•Œ์—๋Š” toBe๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋˜๊ณ  toEqual๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ์•ผ ํ•œ๋‹ค.

test('๊ฐ์ฒด ๋น„๊ตํ•˜๊ธฐ', async () => {
    expect({ id: 1 }).toEqual({ id: 1 });
});

 

๐Ÿซต emit ํ…Œ์ŠคํŠธ

์ž์‹์ปดํผ๋„ŒํŠธ์—์„œ ๋ถ€๋ชจ์ปดํผ๋„ŒํŠธํ•œํ…Œ emit์„ ๋‚ ๋ ค์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. emit์„ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” emitted๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

//ChildComponent.vue
<template>
  <section>
    <article id="childdesc" class="child-desc" @click="click">์ž์‹ article</article>
  </section>
</template>

<script>
export default {
  emits: ['childclick'],
  setup(_, { emit }) {
    const click = () => {
      emit('childclick');
    };

    return {
      click,
    };
  },
};
</script>

<style></style>

 

//childcomponent.test.js
import ChildComponent from '@/components/ChildComponent.vue';
import { shallowMount } from '@vue/test-utils';

let wrapper = null;
beforeEach(() => {
  wrapper = shallowMount(ChildComponent);
});

describe('ChildComponent Testing', () => {
  test('child desc ํด๋ฆญ ํ›„ emit["childclick"] ํด๋ฆญ', async () => {
    wrapper.find('#childdesc').trigger('click');
    expect(wrapper.emitted()).toHaveProperty('childclick');
  });
});

ChildComponent์—์„œ id๊ฐ€ childdesc๋ฅผ ํด๋ฆญํ•˜๋ฉด emit('childclick')์„ ํ†ตํ•ด ๋ถ€๋ชจ๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜, ๋ถ€๋ชจํ•œํ…Œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ผ๋‹จ id๊ฐ€ childdesc๋ฅผ ํด๋ฆญํ•œ๋‹ค.

wrapper.find('#childdesc').trigger('click');

๊ทธ๋ฆฌ๊ณ  emitted์—์„œ childclick๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

expect(wrapper.emitted()).toHaveProperty('childclick');

๊ทธ๋Ÿฌ๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋๋‚œ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

emit ํ…Œ์ŠคํŒ…

 

๐Ÿค˜ router testing

๋ผ์šฐํ„ฐ๋ฅผ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๊ฒ ๋‹ค. router.push๋ฅผ ํ†ตํ•ด ํ™”๋ฉด ์ด๋™ํ•˜๊ณ  ํ™”๋ฉด์ด๋™ํ–ˆ์„ ๋•Œ  findComponent๋ฅผ ํ†ตํ•ด์„œ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

 

import App from '@/App.vue';
import { mount } from '@vue/test-utils';
import ToDoWriteView from '../views/ToDoWriteView.vue';
import router from '@/router/index';
let wrapper = null;

beforeEach(() => {
  wrapper = mount(App, {
    global: {
      plugins: [router],
    },
  });
});


describe('app.vue', () => {
  test('ToDoWriteView ํ™”๋ฉด ์ด๋™', async () => {
    //router๋ฅผ ํ†ตํ•ด ์ด๋™
    await router.push({ name: 'addTodo' });
    //findComponent๋ฅผ ํ†ตํ•ด ์ปดํผ๋„ŒํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
    expect(wrapper.findComponent(ToDoWriteView).exists()).toBe(true);
  });
});

 

๐Ÿค˜ stubs

๋ถ€๋ชจ์ปดํผ๋„ŒํŠธ์—์„œ ์ž์‹์ปดํผ๋„ŒํŠธ๋ฅผ ๋‹จ์ˆœํžˆ ๋ Œ๋”๋ง ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž

import ParentView from '@/views/ParentView.vue';
import RenderComponent from '@/components/RenderComponent.vue';
import { mount } from '@vue/test-utils';

let wrapper = null;

beforeEach(() => {
  wrapper = mount(ParentView, {});
});

describe('ParetnView render', () => {
  test('parente view rendering', async () => {
    //RenderComponent์ด ๋ Œ๋”๋ง ๋˜๋Š”์ง€ ํ™•์ธ
    console.log(wrapper.html());
    expect(wrapper.findComponent(RenderComponent).exists()).toBe(true);
  });
});

 

์œ„ ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•  ๊ฒฝ์šฐ component๋‚ด์˜ outerFuncํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋งŒ์•ฝ api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ๋‹จ์œ„ํ…Œ์ŠคํŠธ์—๋Š” ํ•„์š”์—†๋Š” ๋ถ€๋ถ„์ด๋‹ค. ๋ถ€๋ชจ์ปดํผ๋„ŒํŠธ์—์„œ๋Š” "์ž์‹ ์ปดํผ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง์ด ๋˜๋ฉด ๋œ๋‹ค" ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ฆฌํ•˜์—ฌ "stubs"์„ ํ†ตํ•˜์—ฌ ๊ฐ€์งœ๋กœ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ๋ Œ๋”๋ง ๋˜๋Š”์ง€๋งŒ ์ฒดํฌํ•ด๋ณด์ž.

 

import ParentView from '@/views/ParentView.vue';
import RenderComponent from '@/components/RenderComponent.vue';
import { mount } from '@vue/test-utils';

let wrapper = null;

beforeEach(() => {
  wrapper = mount(ParentView, {
    global: {
      stubs: {
        RenderComponent: true,
      },
    },
  });
});

describe('ParetnView render', () => {
  test('parente view rendering', async () => {
    //RenderComponent์ด ๋ Œ๋”๋ง ๋˜๋Š”์ง€ ํ™•์ธ
    expect(wrapper.findComponent(RenderComponent).exists()).toBe(true);
  });

  test('stubs์„ ์ด์šฉํ•œ ๋ Œ๋”๋ง', async () => {
    console.log(wrapper.html());
    expect(wrapper.findComponent(RenderComponent).exists()).toBe(true);
  });
});

render3

"RenderComponent"๋ฅผ stubs์— ๋‹ด์•„์ฃผ์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๊ณ  ๋‚˜์„œ wrapper.html()์„ ์‚ฌ์šฉํ•ด๋ณด๋ฉด "RenderComponent"์—์„œ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์•ˆํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  findComponent๋ฅผ ํ†ตํ•ด "RenderComponent"๋ฅผ  ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

 

๐Ÿค˜form checkbox multi ์ฒดํฌ testing

์‹ค์ œ๋กœ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ์ฒดํฌํ• ๋•Œ๋Š” ๋‹จ๊ฑด์œผ๋กœ ์ฒดํฌํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ๊ทธ๋ ‡๊ฒŒ ๋งŽ์ง€ ์•Š๋‹ค checkbox๋Š” ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ์„ ํƒํ• ๋•Œ ์‚ฌ์šฉ๋˜๊ณค ํ•œ๋‹ค.

๊ทธ๋ฆฌํ•˜์—ฌ form์—์„œ checkbox๋ฅผ  ์—ฌ๋Ÿฌ๊ฐœ ์„ ํƒํ• ๋•Œ test ์‹œ๋„ํ•ด๋ณด์ž.

 

์ƒํ™ฉ์€ ์ธ์ œ "์ข‹์•„ํ•˜๋Š” ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ทธ๋ฆฌ๊ณ  ์„ ํƒ๋œ ๊ฐ’์„ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ์ž" ์ด๋‹ค.

 

์•„๋ž˜๋Š” ์ข‹์•„ํ•˜๋Š” ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜๋Š” vueํŒŒ์ผ์ด๋‹ค.

1. form์•ˆ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ Input[type=checkbox]๋ฅผ ๋งŒ๋“ ๋‹ค.

2. ์„ ํƒ๋ ๋•Œ ๋งˆ๋‹ค computed๋ฅผ ํ†ตํ•ด์„œ <div id="select-language"></div> ์— ๋ณด์—ฌ์ฃผ์ž

//FormView3.vue
<template>
  <form @submit.prevent="submit">
    <fieldset>
      <legend>์ข‹์•„ํ•˜๋Š” ์–ธ์–ด๋Š”?</legend>

      <div>
        <label for="checkbox1">์ž๋ฐ”</label>
        <input type="checkbox" v-model="formData.checkbox" id="checkbox1" value="java" />
      </div>
      <div>
        <label for="checkbox2">์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ</label>
        <input type="checkbox" v-model="formData.checkbox" id="checkbox2" value="javascript" />
      </div>
      <div>
        <label for="checkbox3">C++</label>
        <input type="checkbox" v-model="formData.checkbox" id="checkbox3" value="c++" />
      </div>
    </fieldset>
  </form>

  <div id="select-language">
    {{ compCheckbox }}
  </div>
</template>
<script>
import { computed, reactive } from 'vue';

export default {
  setup() {
    const formData = reactive({
      checkbox: [],
    });

    const compCheckbox = computed(() => {
      return formData.checkbox.join();
    });

    const submit = () => {};

    return {
      formData,
      submit,
      compCheckbox,
    };
  },
};
</script>
<style scoped></style>

์ธ์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž

1. findํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด checkbox๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

2.setValue ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์„ธํŒ…ํ•ด์ค€๋‹ค.(๋‹ค๋งŒ checkbox๋Š” ์ธ์ž์— ์•„๋ฌด๊ฒƒ๋„ ๋„ฃ์ง€ ์•Š์œผ๋ฉด, value์— ์„ธํŒ…๋œ ๊ฐ’์œผ๋กœ ์„ธํŒ…๋œ๋‹ค.)

3.์„ ํƒ๋œ ๊ฐ’์ด ์ •ํ™•ํ•˜๊ฒŒ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ง€๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

import { shallowMount } from '@vue/test-utils';
import FormView3 from '../FormView3.vue';
describe('formview3 vue', () => {
  let wrapper = null;
  beforeEach(() => {
    wrapper = shallowMount(FormView3);
  });
  test('์ฒดํฌ๋ฐ•์Šค ๊ฐ’ ์„ธํŒ… ๋ฐ ๋น„๊ต', async () => {
    //1. findํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด checkbox๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
    const chkbox1 = await wrapper.find('input[type="checkbox"][id=checkbox1]');
    const chkbox2 = await wrapper.find('input[type="checkbox"][id=checkbox2]');
    // 2.setValue ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์„ธํŒ…ํ•ด์ค€๋‹ค.(๋‹ค๋งŒ checkbox๋Š” ์ธ์ž์— ์•„๋ฌด๊ฒƒ๋„ ๋„ฃ์ง€ ์•Š์œผ๋ฉด, value์— ์„ธํŒ…๋œ ๊ฐ’์œผ๋กœ ์„ธํŒ…๋œ๋‹ค.)
    await chkbox1.setValue();
    await chkbox2.setValue();
    //3.์„ ํƒ๋œ ๊ฐ’์ด ์ •ํ™•ํ•˜๊ฒŒ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ง€๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.
    const selectLanguage = await wrapper.find('#select-language');
    expect(selectLanguage.text()).toBe('java,javascript');
  });
});

์„ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€