Table of Content
๐00. Introduction
์ด๋ฒํธ์์๋ vuex๋ฅผ ํ ์คํ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค. ํด๋น ์ฝ๋๋ vue-test-practice์์ ํ์ธํ ์ ์์ต๋๋ค.๐
ํด๋น ํธ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก Componentํธ์ counter๋ฅผ ๊ตฌํํ๋ ์์ ์์ api mock์ ์ค์ ๋ก ์คํ ํด๋ณด๊ธฐ ์ํด json placeholder์ todos๋ฅผ ๋ถ๋ฌ์ค๋ ์์ ๋ ๊ฐ์ด ์งํํ๋ ค๊ณ ํฉ๋๋ค.
๐ 01. Testing State
state๋ ๋ณดํต getDefaultState
๊ฐ์ ํจ์ํ์ผ๋ก ์ ์ธ์ ํด์ฃผ๋๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ์ด์ ๋ testing์ ํ ๋ ์ข๋ ํธํ๊ฒ state์ ๊ฐ์ ์ด๊ธฐํ ์์ผ์ค ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
// Store.spec.js
import counterStore, { getDefaultState } from '@/store/modules/counter'; // ์คํ ์ด๋ฅผ ๋ถ๋ฌ์ต๋๋ค.
const { state, getters, mutations, actions } = counterStore; // ๋์ค๋ญํฐ๋ง์ ์ฌ์ฉ
// state๋ ํญ์ ์ด๊ธฐ ๊ฐ์ getDefaultState์ ๋ฆฌํด ๊ฐ์ด์ด์ผ ํฉ๋๋ค.
describe('State test', () => {
it('state should be equal getDeFaultState()', () => {
expect(state).toEqual(getDefaultState());
});
});
// counter.js
export const getDefaultState = () => ({
count: 0,
items: [],
});
export default { namespaced: true, state: getDefaultState(), getters, mutations, actions };
๐ 02. Testing getters
getters์๋ ์นด์ดํธ๊ฐ 0์ด๋ ์๋๋๋ ํจ์๋ฅผ ์ ์ธํด๋ณด๊ฒ ์ต๋๋ค.
// Store.spec.js
describe('Getters test', () => {
describe('isCountZero test', () => {
it('count 0 should be true', () => {
const state = { count: 0 };
// count์ ๊ฐ์ด 0์ด๊ธฐ ๋๋ฌธ์ true์ด์ด์ผ ํฉ๋๋ค.
expect(isCountZero(state)).toBeTruthy();
});
it('count 1 should be false', () => {
const state = { count: 1 };
// count ๊ฐ์ด 0์ด ์๋๊ธฐ ๋๋ฌธ์ false์ด์ด์ผ ํฉ๋๋ค.
expect(isCountZero(state)).toBeFalsy();
});
});
});
// counter.js
const getters = {
isCountZero: state => state.count === 0,
};
๐ 03. Testing mutations
๋ค์๊ณผ ๊ฐ์ ๋๊ฐ์ง mutations๋ฅผ ์ ์ธํด๋ณด๊ฒ ์ต๋๋ค.
- INCREMENT: count์ ๊ฐ์ 1์ฆ๊ฐ ์ํด
- DECREMENT: count์ ๊ฐ์ 1๊ฐ์ ์ํด
// Store.spec.js
const { INCREMENT, DECREMENT } = mutations;
describe('Mutation test', () => {
let state;
// ๋งค ํ
์คํธ ์งํ state๊ฐ์ ์ด๊ธฐํ ์์ผ์ค
beforeEach(() => {
state = getDefaultState();
});
describe('Increment test', () => {
it('Increment state by 1', () => {
INCREMENT(state);
// ํ๋ฒ INCREMENT๋ฅผ ํด์ฃผ์์ผ๋ state.count์ ๊ฐ์ 1์ด ๋์์ผํฉ๋๋ค.
expect(state.count).toBe(1);
});
});
describe('Decrement test', () => {
it('Decrement state by 1', () => {
DECREMENT(state);
// ํ๋ฒ DECREMENT๋ฅผ ํด์ฃผ์์ผ๋ state.count์ ๊ฐ์ -1์ด ๋์์ผํฉ๋๋ค.
expect(state.count).toBe(-1);
});
});
});
// counter.js
const mutations = {
[INCREMENT](state) {
state.count += 1;
},
[DECREMENT](state) {
state.count -= 1;
},
};
๐ 04. Testing actions
์ผ๋ฐ์ ์ผ๋ก actions์๋ ๋ณดํต api๊ฐ์ ๋น๋๊ธฐ ์์ ์ด ๋ค์ด๊ฐ๋๋ค. ๋ฐ๋ผ์ json placehoder์์ todos๋ฅผ ๋ถ๋ฌ์ค๋ ์์ ์ ํด๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ todos๋ฅผ ๋ถ๋ฌ์ค๋ api call์ ์ด๋ ๊ฒ ์งค์ ์์ต๋๋ค.
import * as axios from 'axios';
export const fetchData = () => {
return axios.get('https://jsonplaceholder.typicode.com/posts/1'); // 1๊ฐ์ ํฌ์คํธ๋ง ๋ถ๋ฌ์ค๋ api
};
ํ์ง๋ง ์ค์ ํ ์คํธ๋ ํด๋น api์ ๊ฐ์ ๊ทธ๋๋ก ๋ฐ๋ ๊ฒ์ด ์๋ mock์ ํด์ค์ผ ํฉ๋๋ค.
// Store.spec.js
import { fetchData } from '@/api/todo-service';
import { mockItems } from '../__mock__/item-mock';
// ํด๋น ์๋น์ค๋ฅผ jest mock ํด์ค๋๋ค.
jest.mock('@/api/todo-service');
describe('Async actions test', () => {
describe('Fetch items test', () => {
it('get items from mock', async () => {
// ์ด๋ถ๋ถ์์ ๋ด๋ ค์ฌ ๊ฐ์ ์ง์ ์ ํด์ค๋๋ค.
fetchData.mockResolvedValue(mockItems);
// mockItem์ ์ด๋ ๊ฒ ์๊ฒผ์ต๋๋ค.
// export const mockItems = {
// data: [1, 2, 3],
// };
// sinon.spy()์ ์ฌ์ฉ๋ฒ์ด ๊ฐ์ต๋๋ค.
// commit์ด ์ด๋ป๊ฒ ํธ์ถ์ด ๋์๋์ง jest์์ ๊ฐ์ํด์ค๋๋ค.
const commit = jest.fn();
await FETCH_ITEMS({ commit });
expect(commit).toHaveBeenCalledWith('SET_ITEMS', mockItems.data);
});
});
});
// Store.spec.js
const actions = {
async [FETCH_ITEMS]({ commit }) {
const response = await fetchData();
commit(SET_ITEMS, response.data);
},
};
๐ค๋ฒ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ mockํ๊ธฐ
๋ํ์ ์ผ๋ก ์ฐ์ด๋ nanoid๋ฅผ mockํด๋ณด๊ฒ ์ต๋๋ค.
import nanoid from 'nanoid';
/**
* mocking nanoid
*/
jest.mock('nanoid');
let value;
// String์ผ๋ก 1๋ถํฐ ์ฆ๊ฐ๋๊ฒ์ ๋ฆฌํดํฉ๋๋ค.
nanoid.mockImplementation(() => {
return String((value += 1));
});
// ํ
์คํธ๊ฐ ๋๋ํ 0์ผ๋ก ์ด๊ธฐํ๋ฅผ ํด์ค๋๋ค.
beforeEach(() => {
value = 0;
});
// ํ
์คํธ๊ฐ ๋ชจ๋ ๋๋ํ์ ๋ค๋ฅธ ํ
์คํธ์ ์ํฅ์ ๋ฏธ์น ์ ์์ผ๋ฏ๋ก nanoid๋ฅผ ๋๋๋ ค ๋์ต๋๋ค.
afterAll(() => {
nanoid.mockRestore();
});
๐ 05. Conclusion
ํ ์คํธ ์ฝ๋๋ฅผ ์ง ๋ค๋๊ฒ์ ์ดํด๋ณด์์ต๋๋ค. ํ ์คํธ์ฝ๋๋ฅผ ์ง ๋ค๋๊ฒ์ ๊ฐ๊ฐ์ ์ฝ๋๋ฅผ ์ง๋ ๊ฒ์ ์๊ฐ์ด ๋ ๋ค์ด๊ฐ๋ ๊ฒ์ด ์ฌ์ค์ ๋๋ค. ํ์ง๋ง ์ ํํ ์ฝ๋๋ค๋ก ํ๋ํ๋ ์์๊ฐ๋ค๋ฉด ์ ๋ฐ์ ์ธ ์๊ฐ์ ํจ์ฌ ๋ ๋จ์ถ์ํฌ ์ ์์ต๋๋ค. ๊ฐํน mutations์ ๊ฐ์ ๊ฐ๋จํ ๊ฒ๋ค์ ์ test code๋ฅผ ์ง๋์ง ๋ชจ๋ฅด๊ฒ ๋ค๋ ์ฌ๋๋ค์ด ์์ต๋๋ค. ์ด๋ ๋๊ฐ์ง ๊ด์ ์์ ์ข๋ ์ดํด๋ด์ผํ ํ์๊ฐ ์์ต๋๋ค.
์ฒซ ๋ฒ์งธ ๊ด์ ์ test code๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋ฌธ์์ ์ญํ ์ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. mutations์ ์ ํํ๊ฒ ์ด๋ ํ ๊ฐ๋ค์ด ๋ค์ด๊ฐ๋์ง ์ด๋ค ํ์ ๋ค์ด ๋ค์ด๊ฐ๋์ง ์ ํํ๊ฒ ์์์ผ ํ ํ์์ฑ์ด ์์ ๋๊ฐ ์์ต๋๋ค. ํ์ง๋ง ์ค์ ๋ก ๋๋ ค์ ํ์ธํ๊ธฐ์๋ ๋จ๊ณ๊ฐ ๋ณต์กํ ๊ฒฝ์ฐ๋ ์๊ณ ํ๋์ ๋ณด๊ธฐ๊ฐ ์ด๋ ค์ ์ง๋๋ค.
๋ ๋ฒ์งธ ๊ด์ ์ test code๊ฐ ์ฌ์ํ ๋ฒ๊ทธ๋ฅผ ์ก์์ฃผ๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ฐํน ์ ๋ ์ด๋ฐ ์ค์๋ฅผ ํ๊ณค ํฉ๋๋ค. state์ tset๋ผ๊ณ ์ ์ธ
์ด๋ ๊ฒ ์ค์ ์ ํ๊ณ
mutations์๋ ๋ ์ ํํ๊ฒ state.test = 1; ์ด๋ ๊ฒ ํด๋๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ ๋ฒ๊ทธ๋ฅผ ์ก๋๋ฐ ์๊ฐ์ด ์ ๋ง ๋ง์ด ์์๊ฐ ๋ฉ๋๋ค. ์ด๋ฐ ์์ํ ๋ฒ๊ทธ๋ค์ ์ก์์ฃผ๊ธฐ ๋๋ฌธ์ testcode๋ฅผ ์๋๋ค.
ํ์ง๋ง ๋ชจ๋ ์ฝ๋๋ค์ 100% tdd๋ก ๊ฐ๋๊ฒ์ ๋ถ์ ํฉํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ 100% test code๋ฅผ ์ ๋ขฐํ๋๊ฒ ๋ํ ์ํํ๋ค๊ณ ์๊ฐํฉ๋๋ค.(test case์ ๋ถ์ฌ ํน์ test case๊ฐ ์๋ชป๋์์ ๊ฐ๋ฅ์ฑ๋ ์๊ธฐ ๋๋ฌธ์) ํ์ง๋ง ๋ง์น mutations type์ ์์ํ๋ฅผ ํด๋๋ ๊ฒ์ฒ๋ผ ์์ํ ๋ฒ๊ทธ๋ถํฐ ์น๋ช ์ ์ธ ๋ฒ๊ทธ๋ ๊ฝค๋ง์ด ์ก์์ค ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๋ฐ๋ผ์ ํ์์ ๋ฐ๋ผ tdd๋ฅผ ์ฐ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ๋ํ test์ ์ข ๋ฅ๋ ์ฌ๋ฌ๊ฐ์ง ์ ๋๋ค. unit test๋ก๋ง ๋ชจ๋ test๋ฅผ ์ปค๋ฒํ๋ ค๊ณ ํ์ง ๋ง์ธ์ฉ... ๐
์ด๋ฒ ํฌ์คํ ๊น์ง๋ vue์ logic์ ์ธ ๊ฒ์ ์ดํด ๋ณด์๋ค๋ฉด ๋ค์ ํฌ์คํ ์์๋ vue์ view์ ์ธ ๊ฒ์ ํ ์คํธ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.(Story Book)
๐ 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 - Component ํธ (0) | 2020.07.25 |