Table of Content
- Testing vuex in component
- Reference
๐ 00. Introduction
๐จ์ฃผ์: ํด๋น๊ธ์ ํ๊ฒฝ์ค์ ์ ๋ค๋ฃจ์ง๋ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ธ cli๋ก jest๋ฅผ ์ค์ ํ ํ์ผ์ ํ ๋๋ก ์ค๋ช ์ ํฉ๋๋ค.
์ด ํฌ์คํ
์๋ Vue component๋ฅผ ์ด๋ป๊ฒ testing์ ํ ๊น?
์ ๋ํ ๋ฌผ์์ ๋ต๋ณ์ ํ๋ ๊ธ์
๋๋ค. ํด๋น ๊ธ์ ์ฝ๋๋ vue-test-practice์ ๊ฐ์๋ฉด ๋ฉ๋๋ค.
์ด ํฌ์คํ ์์ ๋ค๋ฃฐ ์ปดํฌ๋ํธ๋ ํ๋์ด๋ฉฐ ๊ฐ๋จํ Counter๋ฅผ ๋ง๋ค์ด๋ณด๋ ์์ ๋ก ์ค๋ช ์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ tdd์ ๊ฐ์ฅ ์ค์ํ tdd cycle์ ์ค์ํ์ฌ ์ค๋ช ํฉ๋๋ค.
MyCounter.vue์ ๊ธฐ๋ฅ:
- count๊ฐ์ ํญ์ display๋์ด์ผ ํ๋ค.(์ด๊ธฐ ๊ฐ 0)
- ํ๋ฌ์ค ๋ฒํผ์ ๋๋ฅด๋ฉด count ๊ฐ์ด ์ฆ๊ฐํ๋ค.
- ๋ง์ด๋์ค ๋ฒํผ์ ๋๋ฅด๋ฉด count ๊ฐ์ด ๊ฐ์ํ๋ค.
ํด๋น ๊ธฐ๋ฅ์ ์ค์ ๋ก Data์ Method๋ฐ์ ๊ตฌํ์ด ์๋จ์ผ๋ก ์ถ๊ฐ์ ์ผ๋ก created, props, computed๋ ๊ฐ๋จํ ์์ ๋ก ์ค๋ช ํ๋ ค๊ณ ํฉ๋๋ค.
๐ 01. Testing Data
์ฒซ๋ฒ์งธ๋ก data๊ฐ์๋ count๊ฐ 0์ด ํ์ํฉ๋๋ค. ๋ฐ๋ผ์ ์ด๋ ๊ฒ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋ณผ ์ ์์ต๋๋ค.
//MyCounter.spec.js
import { mount } from '@vue/test-utils';
import MyCounter from '@/components/MyCounter';
describe('MyCounter unit test', () => {
// mount component
const wrapper = mount(MyCounter);
const { vm } = wrapper;
//
describe('MYCOUNTER DATA TEST', () => {
describe('data count test', () => {
it('count should be 0', () => {
expect(vm.count).toBe(0); // data์ count์๋ 0์ด ๋ค์ด์์ด์ผ ํฉ๋๋ค.
});
});
});
}
๐ ๊นจ์์ง์
mount์ shallow mount์ ์ฐจ์ด์ .
mount๋ vue ์ปดํฌ๋ํธ๋ฅผ ๋ฐํํฉ๋๋ค. ๋ฐ๋ผ์ props๋ trigger click์ ์ง์ํฉ๋๋ค.
shallow mount๋ ์์ ์ปดํฌ๋๋ํธ๋ ๋๋๋งํ์ง ์๋๋ค.
// MyCounter.vue
export default {
name: 'MyCounter',
data() {
return {
count: 0,
};
},
};
๐ 02. Testing Methods
counter์ ๊ฐ์ ์กฐ์ ํ๊ธฐ์ํด ๋๊ฐ์ ํจ์๋ฅผ ์ ์ธํ ๊ฒ์ ๋๋ค.
- onClickIncrease (counter์ ๊ฐ์ 1 ์ฆ๊ฐ์ํค๋ ํจ์)
- onClickDecrease (counter์ ๊ฐ์ 1 ๊ฐ์์ํค๋ ํจ์)
describe('MyCounter unit test', () => {
// testing methods
describe('MYCOUNTER METHOD TEST', () => {
// ๋งค ํ
์คํธ ์ ์ count์ ๊ฐ์ 0์ผ๋ก ์ด๊ธฐํ ์ํจ๋ค.
beforeEach(() => {
vm.count = 0;
});
// testing increase method
describe('onClickIncrease test', () => {
it('increase by 1', () => {
vm.onClickIncrease(); // count๋ฅผ ์ฆ๊ฐ ์์ผ๋ณธ๋ค.
expect(vm.count).toBe(1);
});
});
// testing decrease method
describe('onClickDecrease test', () => {
it('decrease by 1', () => {
vm.onClickDecrease(); // count๋ฅผ ๊ฐ์ ์์ผ๋ณธ๋ค.
expect(vm.count).toBe(-1);
});
});
});
});
Vue์ฝ๋๋ ์ด๋ ๊ฒ ์์ฑํด๋ณผ ์ ์์ต๋๋ค.
export default {
name: 'MyCounter',
data() {
return {
count: 0,
};
},
methods: {
onClickIncrease() {
this.count += 1;
},
onClickDecrease() {
this.count -= 1;
},
},
};
๐ 03. Testing Created
๊ธฐ๋ฅ
- created๋ ๋ data.testCreated์ 'TEST_CREATED'๋ฅผ ์ธํ ํด์ค๋ค.
// testing created
describe('MYCOUNTER CREATED TEST', () => {
describe('created test', () => {
it('testCreated data should be changed blank to TEST_CREATED', () => {
expect(vm.testCreated).toBe('TEST_CREATED');
});
});
});
export default {
name: 'MyCounter',
created() {
this.testCreated = 'TEST_CREATED';
},
data() {
return {
testCreated: '',
};
},
};
๐ 04. Testing Props
๋จผ์ props๋ฅผ ์ค์ ํ๊ธฐ ์ํด์๋ ์์์ ์ค๋ช ํ ๊ฒ ์ฒ๋ผ shallow mount๊ฐ ์๋ mount๋ก ์ค์ ์ ํด์ค์ผ ํฉ๋๋ค.
import { mount } from '@vue/test-utils';
import MyCounter from '@/components/MyCounter';
describe('MyCounter unit test', () => {
// mount component
const wrapper = mount(MyCounter, {
propsData: {
testProps: 'TEST_PROPS', // ์ด๋ ๊ฒ ์ค์ ์ ํ ์ ์์ต๋๋ค.
},
});
const { vm } = wrapper;
// testing props
describe('MYCOUNTER PROPS TEST', () => {
describe('testProps test', () => {
it('testProps should be "TEST_PROPS', () => {
expect(vm.testProps).toBe('TEST_PROPS');
});
});
});
});
export default {
name: 'MyCounter',
props: {
testProps: {
type: String,
},
},
};
๐ 05. Testing Computed
๊ธฐ๋ฅ:
data.tempText('HELLO')๋ฅผ ์๋ฌธ์๋ก ์ถ๋ ฅํ๋ computed
describe('MYCOUNTER DATA TEST', () => {
// testing computed
describe('MYCOUNTER COMPUTED TEST', () => {
it('lowercaseTempText test', () => {
expect(vm.lowercaseTempText).toBe('hello');
});
});
});
export default {
name: 'MyCounter',
data() {
return {
tempText: 'HELLO',
};
},
computed: {
lowercaseTempText() {
return this.tempText.toLowerCase();
},
},
};
๐ Testing vuex in component
component์์ vuex์ test ์์์ mock์์ ๋ถํฐ ์ถ๋ฐ ํฉ๋๋ค. ์ค์ ๋ก ๋ก์ง์ด ์ํ์ด ๋๋๊ฑด ๋ฐ๋ก vuex์์ ํ ์คํธ ํด์ฃผ์ธ์.
// ๋จผ์ vuex๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import MyCounter2 from '../../src/components/MyCounter2.vue';
// ์ด๋ ๊ฒ localVue์์ vuex๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค.
const localVue = createLocalVue();
localVue.use(Vuex);
describe('MyCounter2 test', () => {
let store;
let state;
let actions;
let mutations;
let wrapper;
let vm;
beforeEach(() => {
state = {
count: 0,
};
mutations = {
['INCREMENT']: jest.fn(),
['DECREMENT']: jest.fn(),
};
actions = {
['FETCH_ITEMS']: jest.fn(),
};
store = new Vuex.Store({
modules: {
counter: {
namespaced: true,
state,
mutations,
actions,
},
},
});
wrapper = mount(MyCounter2, { store, localVue });
vm = wrapper.vm;
});
it('count mock', async () => {
expect(vm.count).toBe(0);
});
it('mutations mock', () => {
const incBtn = wrapper.find('#incBtn');
incBtn.trigger('click');
expect(mutations.INCREMENT).toHaveBeenCalled();
const decBtn = wrapper.find('#decBtn');
decBtn.trigger('click');
expect(mutations.DECREMENT).toHaveBeenCalled();
});
it('actions mock', () => {
const fetchBtn = wrapper.find('.fetchBtn');
fetchBtn.trigger('click');
expect(actions.FETCH_ITEMS).toHaveBeenCalled();
});
});
๐ Conclusion
๋ทฐ์ ์ปดํฌ๋ํธ ํ ์คํธ๋ ์ฌ์ค ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์์ต๋๋ค.. ํ์ง๋ง ์ค๋ฌด์์ ์ ์ฉํ๊ธฐ๋ ์๋นํ ์ด๋ ต์ต๋๋ค. ๊ทธ์ด์ ๋ ๊ฐ์ข ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค๊ณผ utils๋ค ๋๋ฌธ์ ์์กด์ฑ ๋ฌธ์ ๋๋ฌธ์ test๊ฐ ์ด๋ ค์์ง๋๋ค. ์ด๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ต๋ํ logic์ store๋ก ๋นผ์ logic์ ์ธ unit test๋ store์์ ์งํํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ๋ ๊ฒ View์ Store๋ฅผ ๋ถ๋ฆฌ๋ฅผ ํ์ฌ View์์๋ snapshot ํ ์คํธ, storybook, ํน์ e2eํ ์คํธ๋ก ์งํํ๋ ๊ฒ์ด ์ข๊ณ store๋ unit์ผ๋ก ํ ์คํ ์ ํ๋๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ๋์ ๋ค์ ํฌ์คํ ์์๋ store๋ฅผ ํ ์คํธ ํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
Reference
'Vue TDD' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Vue TDD - ํ๊ณ ํธ (0) | 2020.08.10 |
---|---|
Test code ์ฝ๊ฒ ์ฐ๊ธฐ - 1 (0) | 2020.08.06 |
Vue TDD - E2E ํธ (0) | 2020.08.02 |
Vue TDD - Storybook ํธ (0) | 2020.07.31 |
Vue TDD - Vuex ํธ (0) | 2020.07.30 |