Jest로 Nuxt Store 스토어 테스트 만들기 (모듈화, 네임스페이스 사용)

Nuxt.js·2021년 8월 31일
5

NUXT기초 배우기

목록 보기
14/20

도입

넉스트를 설치 하고 프로젝트를 생성 하면 테스트 프레임워크를 옵션으로 선택 할 수 있다. 친절하게도 샘플 테스트도 같이 딸려온다. 하지만 상용 웹앱을 제작 하거나 꽤나 규모가 있는 프로젝트를 진행 중이라면 대부분의 메인로직이 Vuex 스토어(Stores)에 저장 되어 있을 것이다. 안타깝게도 샘플 테스트에는 스토어 테스트가 포함 되어 있지 않다. 난감하다. 어떻게 해야 할지 구글링을 해도 답이 잘 나오질 않는다.
구체적인 예제와 방법을 이번 포스팅에서 알아 보도록 하자.

문제점

Nuxt.js는 기존 Vue.js에서 라우팅 이라던가 불편하던 것들을 많이 해결 해준다. 그리고 수많은 개발자들은 Vuex를 모듈화로 쪼개서 사용 하고 있을 것이다. 그래서 두개 이상의 index.js를 /stores폴더 아래에 가지고 있을 텐데 넉스트가 내부적으로 이를 빌드 해주고 있다.
넉스트에서는 오브젝트 문맥으로 이 스토어에 접근이 가능하다. (store.모듈.액션 이라던가) 그리고 Vuex 헬퍼도 이용 가능하다. (mapGetters, mapMethods, mapActions 등)
그리고 이를 테스트 하기 위해서 테스트코드를 짜본 사람이라면 잘 알텐데 실제로 이용 할때의 스토어 상태와 테스트 할때의 스토어 상태가 달라서 어려운점이 한 둘이 아니다.

예를 들어 아래와 같이 asks테스트 스펙에서는 asks스토어 하나를 목킹 해서 사용 하지만 실제로 asks 페이지에서는 세션 관리를 위해서 user 등 복수의 모듈을 사용 하고 있다.

혹은 스토어 목업을 아래처럼 지저분하게(?) 매번 해야 한다.

이를 해결 하기 위해서 수많은 삽질을 했다.
근본적으로는, 이미 페이지에서 스토어를 잘 사용 하고 있는데 왜 테스트에서 별개로 다시 스토어를 짜야 하나? 기존에 쓰는 방법 그대로 쓸수는 없을까? 에 대한 궁금함에서 이 포스팅을 쓰게 되었다.

해결 방법

두가지 포인트가 있다.
1. 셋업 파일을 미리 설정 하고 jest test가 뭘 하던간에 처음에 무조건 셋업을 부른다.
2. 빌드 된 스토어 파일을 동적으로 테스트 파일에 가져온다.

jest.config.js 파일이 제스트 환경 설정을 담당 하는데 여기에 globalSetup 레퍼런스를 추가 하자. 값은 각자 원하는 파일명을 주면 된다. 여기서는 "<rootDir>/jest.setup.js" 로 지정 해보겠다.

jest.config.js

require('dotenv').config()
module.exports = {
  globalSetup: '<rootDir>/jest.setup.js',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/$1',
    '^~/(.*)$': '<rootDir>/$1',
    '^vue$': 'vue/dist/vue.common.js',
    'vuetify/lib(.*)': '<rootDir>/node_modules/vuetify/es5$1',
  },
  moduleFileExtensions: ['js', 'vue', 'json'],
  testMatch: [
    '<rootDir>/tests/jest/**/*.spec.js',
    '<rootDir>/tests/jest/**/*.test.js',
  ],
  transform: {
    '^.+\\.js$': 'babel-jest',
    '.*\\.(vue)$': 'vue-jest',
  },
  collectCoverage: false,
  collectCoverageFrom: [
    '<rootDir>/components/**/*.vue',
    '<rootDir>/pages/**/*.vue',
  ],
  testTimeout: 10000,
  transformIgnorePatterns: [
    '<rootDir>/node_modules/(?!(vuetify)/)',
    '<rootDir>/node_modules/(?!typed-vuex)',
  ],
}

어떤 테스트가 실행 되던간에 jest.setup.js파일을 한번 부르게 한다.
그리고 넉스트가 스토어 빌드를 돌리게 하는게 핵심이다.
파일 내용은 기존 nuxtConfig 를 오버라이드 하자.
아래와 같으며 환경변수 부분도 중요하니 빼먹으면 안된다.

import { Nuxt, Builder } from 'nuxt'
import nuxtConfig from './nuxt.config'

// 스토어 빌드만을 위해서 나머지 옵션들을 끄자.
const resetConfig = {
  loading: false,
  loadingIndicator: false,
  fetch: {
    client: false,
    server: false,
  },
  features: {
    store: true,
    layouts: false,
    meta: false,
    middleware: false,
    transitions: false,
    deprecations: false,
    validate: false,
    asyncData: false,
    fetch: false,
    clientOnline: false,
    clientPrefetch: false,
    clientUseUrl: false,
    componentAliases: false,
    componentClientOnly: false,
  },
  build: {
    indicator: false,
    terser: false,
  },
}

// SPA모드로, 위에 오버라이드 한 config를 집어넣는다.
const config = Object.assign({}, nuxtConfig, resetConfig, {
  ssr: false,
  srcDir: nuxtConfig.srcDir,
  ignore: ['**/components/**/*', '**/layouts/**/*', '**/pages/**/*'],
})

const buildNuxt = async () => {
  const nuxt = new Nuxt(config)
  await new Builder(nuxt).build()
  return nuxt
}

module.exports = async () => {
  const nuxt = await buildNuxt()

  // 나중에 테스트에서 사용 하기 위해서 PATH를 따로 환경변수에 저장 해놓자.
  process.env.buildDir = nuxt.options.buildDir
}

store/consults/getters.js

export default {
  getConsults: (state) => state.consults || [],
}

store/consults/index.js

import actions from './actions'
import getters from './getters'
import mutations from './mutations'

export const state = () => ({
  consults: [],
})

export default {
  namespaced: true,
  state,
  actions,
  mutations,
  getters,
}

store/consults/mutations.js

export default {
  setConsults(state, consults) {
    state.consults = consults
  },
}

마지막으로 테스트를 작성하자.

tests/jest/consults/stores.spec.js

import _ from 'lodash'
import Vuex from 'vuex'
import { createLocalVue } from '@vue/test-utils'
/*
yarn test tests/jest/consults/stores.spec.js
*/

describe('[consults] 스토어 유닛 테스트', () => {
  const localVue = createLocalVue()
  localVue.use(Vuex)
  let NuxtStore
  let store

  beforeAll(async () => {
    const storePath = `${process.env.buildDir}/store.js`
    NuxtStore = await import(storePath)
  })

  beforeEach(async () => {
    store = await NuxtStore.createStore()
  })

  describe('getConsults', () => {
    let cons

    beforeEach(() => {
      cons = store.getters['consults/getConsults']
    })

    test('[유닛테스트] 게터는 어레이를 획득 할 수 있어야 한다.', () => {
      expect(_.isArray(cons)).toBe(true)
    })

    test('[유닛테스트] 초기화 배열 길이가 0이어야 한다.', () => {
      expect(cons.length).toBe(0)
    })

    test('[유닛테스트] 길이 4의 배열을 셋 했으면 게터에 반영이 되어야 한다.', () => {
      store.commit('consults/setConsults', [1, 2, 3, 4])
      const cons2 = store.getters['consults/getConsults']
      expect(cons2.length).toBe(4)
    })
  })
})

터미널로 이동 해서 테스트를 실행 해보자.

문제 없이 잘 실행되는 것을 알 수 있다.
beforeAll 블록에서 환경변수를 이용해서 동적으로 빌드된 스토어를 가져오고 있다. beforeEach 블록에서 매번 새로운 스토어를 만들고 있으므로 각 테스트는 독립적으로 운영 되게 된다.

profile
Nuxt.js

0개의 댓글