before와 after를 테스트 파일에서 오직 한번 만 사용하기 위해서는 beforeAll와 afterAll를 사용하면 된다.
1 2 3 4 5 6 7
beforeAll(() => { console.log('테스트 전 오직 한번만 실행된다.'); });
afterAll(() => { console.log('테스트 후 한번만 호출된다.'); });
describe 내부에서의 before, after 사용하기
describe 내부에서 before와 after를 사용하는 경우에는 describe 내부의 테스트가 호출 될 경우에만 호출된다. 하지만 describe 외부의 before와 after는 describe 내부의 test를 포함한 테스트 파일 전체의 test가 호출 될때 호출이 된다.
test.only 사용하기
테스트가 실패할때 가장 먼저 확인 해봐야 할것은 테스트 케이스가 오직 실패하는 케이스만 실행 했을때에도 실패하는지 이다. 이를 위해서 test.only를 사용하면 다른 테스트는 스킵하고 해당 테스트만 실행된다. 이때 스킵되는 테스트는 test.only가 존재하는 테스트 파일에 한해서이다.
일반적인 상황에서 비동기를 사용하는 보편적인 방법 중 하나는 콜백을 사용하는 것이다. 하지만 Jest에서 테스트 할때는 의도대로 작동 하지 않는다. Jest 테스트는 콜백을 호출할때까지 기다려주지 않는다. 테스트 함수 내에서 마지막 코드까지 실행을 하게되면 테스트를 완료하게 된다. 그래서 항상 테스트가 통과하게 된다. 테스트 실패하도록 값을 변경하여도 통과하는 것을 확인 할수있다.
promise의 reject 또한 테스트 할수 있다. 이경우에는 then 대신에 catch를 활용한다. catch 내부에서 테스트 할 코드를 작성한다. 이때 expect.assertions(에러 발생한 횟수)를 테스트 위에 적어주지 않으면 테스트는 정상적으로 실행되지 못하고 완료되어 버린다.
아래의 컴포넌트는 vuex의 actions를 사용하는 코드이다. Vuex를 사용하는 컴포넌트를 테스트시에 주의해야 할 점은 actions가 어떻게 동작하는지는 중요하지 않다. 우리가 사용하고자 하는 action이 실제로 호출되었는지 여부만 테스트 한다. action이 정확하게 동작하는지 여부는 vuex store 테스트시에 하게 된다.
it('dispatches "actionInput" when input event value is "input"', () => { const input = wrapper.find('input'); input.element.value = 'input'; input.trigger('input'); expect(actions.actionInput).toHaveBeenCalled(); });
it('does not dispatch "actionInput" when event value is not "input"', () => { const input = wrapper.find('input'); input.element.value = 'no input'; input.trigger('input'); expect(actions.actionInput).not.toHaveBeenCalled(); });
it('calls store action "actionClick" when button is clicked', () => { const button = wrapper.find('button'); button.trigger('click'); expect(actions.actionClick).toHaveBeenCalled(); }) });
첫번째 주의해야 할 점은 테스트 시에는 localVue를 사용해야 한다는 것이다. 두번째 는 actions를 jest의 mock functions를 사용하여 테스트한다. 이를 이용하면 actions를 실제로 구현하지 않아도 호출되었는지 여부를 확인 할수 있다. 세번째 는 함수의 호출 여부를 확인 하는 assertion은 toHaveBeenCalled를 사용한다.
getters를 mock 하기
getters도 actions와 마찬가지로 어떻게 동작하는지는 중요하지 않다. getters의 결과가 실제로 렌더링 되는지 여부만 확인 해보면 된다.
describe('Getters.vue', () => { let getters:any; let store:any; let wrapper:Wrapper<Vue>;
beforeEach(() => { getters = { clicks: () =>2, inputValue: () =>'input' }; store = new Vuex.Store({ getters }); wrapper = shallowMount(VuexComponent, { store, localVue }); }); it('Renders "store.getters.inputValue" in first p tag', () => { expect(wrapper.find('.input-value').text()).toBe(getters.inputValue()); });
it('Renders "store.getters.clicks" in second p tag', () => { expect(wrapper.findAll('p').at(1).text()).toBe(getters.clicks().toString()); }); });
첫번째 는 actions와는 달리 getters는 jest의 mock functions를 사용하지 않는다. getters 객체를 작성하지만 로직은 중요하지 않는다. 두번째 는 getters의 리턴 값이 렌더링되었는지 여부를 확인 하는 건 wrapper의 text 메소드를 이용한다.
it('calls store action "moduleActionClick" when button is clicked', () => { wrapper.find('button').trigger('click'); expect(actions.moduleActionClick).toHaveBeenCalled(); })
it('renders "state.clicks" in first p tag', () => { expect(wrapper.find('p').text()).toBe(state.clicks.toString()); }); });
Vuex Store 테스트하기
지금까지는 Vuex를 사용하는 컴포넌트에 대해서 알아보았다. 그렇기 때문에 실제 Vuex의 내부 동작과는 무관한 테스트였다. 이번에는 Vuex가 정확하게 동작하는지 여부를 테스트 하기 위한 방법이다. Vuex Store 테스트는 두가지 방법이 있다. 첫번째는 getters, mutations, actions를 독립적으로 테스트 하는 것이다. 두번째는 실제 Vuex Store를 생성하는 방법이다. 아래는 테스트에서 사용할 mutations와 getters 코드이다.
독립적으로 테스트 하는 경우에는 좀더 상세하게 테스트를 진행할수 있다. 테스트가 실패하더라도 어디에서 실패했는지 찾기가 쉽다. 단점으로는 commit, dispatch같은 Vuex의 함수들을 mock 해야할 필요가 있다. 이는 유닛 테스트는 성공할지라도 mock이 정확하지 않기 때문에 실제 production 코드는 실패할수도 있다.
1 2 3 4 5 6 7
import mutations from'../mutations';
test('"increment" increments "state.count" by 1', () => { const state = { count: 1 }; mutations.increment(state); expect(state.count).toBe(2); });
1 2 3 4 5 6 7 8 9 10 11
import getters from'../getters';
test('"evenOrOdd" returns even if "state.count" is even', () => { const state = { count: 2 }; expect(getters.evenOrOdd(state)).toBe('even'); });
test('"evenOrOdd" returns odd if "state.count" is odd', () => { const state = { count: 1 }; expect(getters.evenOrOdd(state)).toBe('odd'); });
store를 사용한 테스트
실제로 Vuex sotre를 사용한 방법이다. 이 테스트의 장점은 Vuex function들을 mock 할 필요가 없다는 것이다. 하지만 테스트 실패시에 어디에서 문제가 발생했는지 찾기가 어렵다.
const localVue = createLocalVue(); let store:any; localVue.use(Vuex);
beforeEach(() => { store = new Vuex.Store(cloneDeep(storeConfig)); });
test('increments "count" value when "increment" is committed', () => { expect(store.state.count).toBe(0); store.commit('increment'); expect(store.state.count).toBe(1); })
test('updates "evenOrOdd" getter when "increment" is committed', () => { expect(store.getters.evenOrOdd).toBe('even'); store.commit('increment'); expect(store.getters.evenOrOdd).toBe('odd'); })
해당 테스트에서는 cloneDeep를 사용하고 있는데 이는 각 테스트에 store를 클린하게 사용하기 위해서 이다.
그리고 생각해 보았을 때 상황에 맞추어서 하나씩만 적용 하던가 아니면 두개 모두를 적용해서 테스트 코드를 작성 하면 좋을거 같다.
vue 라우터를 테스트 할 시에 global Vue에 직접 추가를 해서는 안된다. vue 라우터를 설치 할 경우에는 $route와 $router가 vue 프로퍼티에 추가되어진다. 이는 $route와 $router를 mock 해서 테스트 하는 경우에 테스트를 실패하게 만든다. vue-test-utils에서 제공하는 createLocalVue을 사용하는 것을 권장한다.
TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option): src/components/__tests__/HelloWorld.test.ts:12:23 - error TS2339: Property 'message' does not exist on type 'CombinedVueInstance<Vue, object, object, object, Record<never, any>>'.
그래서 정상적으로 테스트 하기 위한 방법은 두가지 정도 인거 같다.
1. vm.$data 사용하기
vm에는 data값을 가지고 있는 $data 객체가 존재한다. 해당 객체를 통해서 검증을 하면 가능하다.
1
expect(wrapper.vm.$data.message).toBe('message');
2. jest.config.js 수정하기
ts-jest를 사용하면 diagnostics옵션의 디폴트값이 true인데 위 코드 처럼 사용할 경우 에러를 발생시킨다. 이를 false로 설정하면 정상적으로 테스트가 가능하다.
테스트 코드를 작성하다 보면 테스트마다 공통적으로 사용이 되는 코드가 존재한다. 그중에서 테스트 코드 로직을 실행 하기 이전에 실행이 되어야 하는 공통 코드가 있다. 이때는 사용되는 함수가 beforeEach 함수이다. 해당하는 함수는 각 테스트 코드가 실행되기 이전에 호출된다.
1 2 3
beforeEach(() => { // 보통 변수의 초기화 등의 코드가 들어가거나 테스트 코드 전에 실행되어야 할 로직이 들어간다. });
테스트 종료시 호출
각 테스트가 종료 후에 호출이 되는 함수도 존재한다. 이는 afterEach 함수이다.
1 2 3
afterEach(() => { // 테스트 종류 후에 작업 해야 할 로직이 들어간다. })