๐ฆถ ํจํค์ง ์ค์น
node : 16.8.1
npm : 8.19.2
๐ค vue jest ์ค์น
๐ค 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("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');
๊ทธ๋ฌ๋ฉด ํ ์คํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋๋๊ฑธ ์ ์ ์๋ค.
๐ค 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);
});
});
"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');
});
});
์ฑ๊ณต์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธ ํ ์ ์๋ค.
'Vue' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐ฒ vue + jest๋ฅผ ์ด์ฉํ login ๊ตฌํ (0) | 2023.04.11 |
---|---|
๐ vue jest router ์๊ธด ๋ฌธ์ (0) | 2023.03.16 |
๐ vue์์ scss๋ฅผ ํตํด ๊ฐ๋จํ๊ฒ theme๋ณ๋ก ๊ด๋ฆฌํด๋ณด๊ธฐ (1) | 2023.01.24 |
๐ vuex ๋ชจ๋ํ (0) | 2023.01.18 |
๐ vee-validate ์ ์ฉ (0) | 2022.08.07 |
๋๊ธ