Jest와 VTU로 Vue 테스트하기(1)

tkppp·2022년 1월 13일
0

스프링5와 Vue.js2로 시작하는 모던 웹 애플리케이션 개발

이놈의 책은 Vue2 기반으로 쓰여진 책이라서 그런지 예제 코드가 안되는 경우가 빈번하다. 비록 백엔드를 지망하지만 풀스택에 대한 의지이 어느정도 있기 때문에 부족한 Vue 공부 겸 Vue3로 테스트를 정리해보자.

VTU(Vue Test Utils)

Vue 인스턴스 생성

Vue 생성자를 이용해 Vue 인스턴스를 생성하고 인스턴스 메소드 $mount를 이용하는 방법도 있지만 VTU에서 제공하는 함수를 통해 쉽게 Vue 인스턴스를 생성하고 마운트할 수 있다.

import Vue from 'vue'
import RegisterPage form '@/views/RegisterPage'

// 생성자 기반
const Constructor = Vue.extend(ReigsterPage)
const vm = new Constructor().$mount

// VTU 사용
import { mount } form '@vue/test-utils'

const wrapper = mount(RegisterPage)

VTU의 mount(), shallowMount() 메소드를 이용해 뷰 인스턴스를 생성하고 마운트할 수 있다. 이 메소드는 테스팅을 위한 wrapper 객체를 반환한다. 일반적인 Vue 인스턴스와는 다르니 구별해야 한다.

mount와 shallowMount의 차이

mount() 메소드는 하위 컴포넌트까지 모두 렌더링하지만 shallowMount() 메소드는 하위 컴포넌트는 렌더링하지 않는다(stub).

아래는 wrapper 객체의 주요 프로퍼티와 메소드의 설명이다.

프로퍼티 or 메소드설명비고
.vmVue 인스턴스를 반환
.element루트 요소를 반환document.getElementByXXXX 에서 반환되는 element
.attributes(속성)루트 요소의 HTML 속성 객체 또는 속성 값을 반환
.classes(클래스)루트 요소의 HTML 클래스 속성값의 배열 혹은 속성값의 유무 반환
find(선택자 or 컴포넌트)특정 css 선택자 혹은 컴포넌트의 wrapper 객체를 반환querySelector와 유사하지만 반환 값이 wrapper
.findAll(선택자 or 컴포넌트)특정 css 선택자 혹은 컴포넌트의 wrapper 객체 배열을 반환
.isVisible()(Deprecaed) 보이는 요소인지 확인(display: none 혹은 visibility: hidden이면 false)
.html()HTML을 문자열로 반환innerHTML과 유사
.text()Text를 반환innerText와 유사
.setValue()input 요소의 값을 지정(반응성, v-model로 바인딩된 데이터 값도 업데이트)비동기 함수
.setData({ 키: 값 })data에 값을 할당비동기 함수
.trigger이벤트를 트리거비동기 함수

자세한 내용은 공식문서관련 포스트를 참조

VTU에서 Vue Router 테스트하기

// Vue2 VTU
import { mount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'
import router from '@/router'
import RegiserPage from '@/views/RegisterPage'

const localVue = createLocalVue()
localVue.use(VueRouter)

const wrapper = mount(App, {
  localVue,
  router,
})

Vue2에서는 전역 Vue 인스턴스를 건드리지 않도록 createLocalVue( ) 를 이용해 테스트에 사용할 로컬 Vue 클래스를 만들어 해당 로컬 인스턴스에 뷰 라우터를 등록하여 사용했는데 Vue3에서는 해당 메소드가 아예 사라져버렸다.

Vue3에서 뷰 라우터를 테스트하기 위해서는 두가지 방법이 있다. 가짜 뷰 라우터(Mocking)를 만들어 사용하거나 실제 뷰 라우터를 가져와 plugins에 등록하여 사용한다.

실제 라우터 사용하기

// Vue3 VTU
import { mount } from '@vue/test-utils'
import router from '@/router'
import RegiserPage from '@/views/RegisterPage'

const wrapper = mount(RegisterPage, {
  global: {
    plugins: [router]
  }
}

위의 createLocalVue( )를 사용한 방식과 동일하다. localVue를 등록하는 방식에서 플러그인(Vuex, router 등), 믹스인 등을 global 옵션에서 설정하여 사용한다. 또한 stubs과 mocks 옵션도 global 옵션에서 설정한다.

라우터 테스트에서 유의할 점은 뷰 라우터가 비동기적으로 동작한다는 것이다. 따라서 await router.isReady( ) 를 통해 라우터가 준비될때까지 기다려야 한다.

it('routing', async () => {
    const wrapper1 = mount(App, {
      global: {
        plugins: [router]
      }
    })

    router.push('/login')
    await router.isReady()
    
    expect(wrapper1.find('h1').text()).toEqual('TaskAgile')
})

가짜 라우터 사용하기(모킹)

라우팅의 동작에는 관심이 없고(페이지 이동이 잘 되는지 여부) 라우팅 이벤트가 잘 실행되는지만을 확인하고 싶다면 뷰 라우터의 세부정보가 필요하지 않다. 단순히 뷰 라우터에서 실행되는 특정 메소드만을 모킹하여 등록하면 된다.

const mockRouter = {
    push: jest.fn()
}

const wrapper = mount(RegisterPage, {
    global: {
        mocks : {
            $router: mockRouter
        }
    },
});

만약 테스트에서 $router.push 메소드의 실행여부만 알고 싶다면 실제 뷰 라우터를 플러그인에 등록하지 않고 모킹한 함수를 global 옵션의 mocks 옵션에 추가하면 되는 것이다.

Jest

describe, it(test)

describe는 테스트의 범위를 설정하고 it(test)는 단위 테스트를 설정한다.

describe('테스트 범위', () => {
  // it과 test는 동일
  it('단위 테스트 1', () => {})
  test('단위 테스트 2', () => {})
})

Hooks

훅 함수를 통해 테슽트 코드 전후를 제어할 수 있다.

  • beforeAll, afterAll : describe 범위 내에서 전후 동작. 즉 describe 전후로 딱 한번만 실행됨.
  • beforeEach, afterEach : describe 범위 내에서 test 단위 전후로 동작.

only, skip

describe와 test에 대해서 일부 테스트만 실행하거나 특정 테스트를 스킵할 때 사용

Matchers

expect(받은 값) 을 통해 받은 값을 확인하는 용도로 사용됨.

주요 Matcher는 다음과 같다.

공통

Matcher설명비고
.toBe(값)받은 값과 기본 동등 비교원시 데이터 비교
.toEqual(값)받은 값과 깊은 동등 비교===

toHave

Matcher설명비고
.toHaveBeenCalled()함수가 호출되었는지 확인
.toHaveBeenCalledTimes(횟수)함수가 몇번 호출되었는지 확인
.toHaveBeenCalledWith(인수...)함수가 해당 인수와 함께 호출되었는지 확인

Mocking

모킹이란 단위 테스트에서 의존하는 특정 부분을 가짜로 대체하는 기법이다. 일반적으로 테스트하려는 코드가 의존하는 부분을 직접 생성하기가 너무 부담스러운 경우(데이터베이스 호출 등 비용이 많이 드는 경우) mocking을 사용한다.

jest.fn( )

jest.fn() 을 통해 가짜 함수를 만들 수 있다.

const mockFn = jest.fn()

만들어진 가짜 함수는 인자를 받아 실행 할 수 있다.

mockFn()
mockFn(1,2)

가짜 함수의 기본 반환 값은 undefined로 mockReturnValue() 메소드를 통해 반환값을 지정할 수 있다. 또 mockResolvedValue() 메소드를 통해 비동기 가짜 함수를 만들 수 있다.

이렇게 가짜 함수가 유용한 이유는 자신이 어떻게 호출되었는지 기억하기 때문이다.

mockFn("a");
mockFn(["b", "c"]);

expect(mockFn).toBeCalledTimes(2);
expect(mockFn).toBeCalledWith("a");
expect(mockFn).toBeCalledWith(["b", "c"]);

jest.spyOn( )

어떤 객체의 메소드의 구현을 대체하지 않고 해당 메소드의 호출 여부와 어떻게 호출되었는지를 파악하기 위해서 jest.spyOn(objectName, methodName: string) 을 통해 해당 메소드를 감시할 수 있다.

const spyFn = jest.spyOn(wrapper.vm, 'submitForm')

await buttonSubmit.trigger('submit');
expect(spyFn).toBeCalled();

jest.mock( ) - 모듈 모킹

jest.fn(), jest.spyOn() 을 통해 함수를 대체하거나 함수를 감시할 수 있지만 모듈 혹은 외부 모듈(라이브러리)를 모킹하려면 모듈의 모든 함수를 jest.fn() 대체하던지 해야하기 때문에 매우 번거롭다. jest.mock(moduleDir) 는 모듈의 모든 함수를 가짜 함수로 만들어준다.

또 모킹하려는 모듈의 디렉토리에 __mocks__/moduleName 을 생성해 커스터마이징을 할 수도 있다.

// /src/services/registration/index.js
export default {
  register (detail){
    return new Promise((resolve, reject) => {
      resolve()
    })
  }
}

// /src/services/registration/__mocks/index.js
export default {
  register (detail){
    return new Promise((resolve, reject) => {
      detail.emailAddress === 'gowldla0423@naver.com'
        ? resolve({result: 'success'}) : reject(new Error('이미 등록된 사용자입니다'))
    })
  }
}

// test1.spec.js
// 모듈의 register() 함수가 __mocks/index.js의 커스터마이징 가짜 함수로 변경
jest.mock('@/services/registration');

0개의 댓글

관련 채용 정보