Madogiwa Blog

主に技術系の学習メモに使っていきます。

Nuxt.jsのプロジェクトにJestを導入するMEMO👢

最近Nuxt.jsで作成しているアプリケーションに後からjestを導入したので、その辺の手順をメモしておきます📝

Jestとは?

JestはJavaScriptのテスティングフレームワークです。

jestjs.io

基本的な構文は下記のような感じです、若干Rspecに近い感じがしますね👀

const sum = function sum(a, b) {
  return a + b;
}

describe('sample spec', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
})

詳しい説明等は公式のドキュメントをご確認ください🙋

jestjs.io

Nuxt.jsにJestを導入する

前提

今回のNuxt.jsとTypeScriptとJestのバージョン情報は下記のとおりです。

  • nuxt: 2.10.2
  • typescript: 3.7.4
  • jest: 25.1.0

依存ライブラリのインストール

まず使用するためにJest本体と関連ライブラリをinstallします📦

// Jest本体とVue,TypeScriptのサポート用のライブラリをインストール
npm install --save-dev jest ts-jest vue-jest @vue/test-utils @types/jest 
// Jest内で最新のJS構文を使用するためにBabel関連のライブラリもインストール
npm install --save-dev npm install --save-dev babel-jest babel-core babel-preset-env

Jestの設定

Jestを使うにあたっての設定は特に必要なありません 🙆‍♂️

zero config Jest aims to work out of the box, config free, on most JavaScript projects. https://jestjs.io/ja/

※必要に応じてjest --initでデフォルトの設定ファイルを作成してカスタマイズ可能

なのでJestでBabelを使うための設定飲みを行います⚙

Babelの設定は下記のような.babelrcをプロジェクトのルートディレクトリに配置すればOKです👍

{
  // babel-preset-envを使用することを明示、変換先をfalse(何もしない)に指定
  "presets": [["env", { "modules": false }]], 
  "env": {
    "test": {
      // babel-preset-envを使用することを明示、対象環境をnodeに指定
      "presets": [["env", { "targets": { "node": "current" } }]] 
    }
  }
}

参考:

Jestの実行コマンドを追加

npm run testでJestが実行できるようにpackage.jsonscriptを追加します🏃

  "scripts": {
    "test": "jest --config jest.config.js spec/**/*.js"

テスト用のファイル.spec.jsを追加

今回は下記のようなVueコンポーネントをテストするSpecをサンプルで追加しました。

import Vue from 'vue'
import Vuetify from 'vuetify'
import { mount } from '@vue/test-utils'
import Favorite from '@/components/foods/Favorite.vue'
Vue.use(Vuetify)

describe('components/Favorite.vue', () => {
  it('is a Vue instance', () => {
    const wrapper = mount(Favorite)
    expect(wrapper.isVueInstance()).toBeTruthy()
  })

  describe('toggeleFavorite', () => {
    describe('is not favorited.', () => {
      it('toggle favorited.', () => {
        const wrapper = mount(Favorite, { propsData: { favorited: false } })
        wrapper.vm.toggeleFavorite()
        expect(wrapper.vm.currentFavorited).toBe(true)
      })
    })

    describe('is favorited.', () => {
      it('toggle unfavorited.', () => {
        const wrapper = mount(Favorite, { propsData: { favorited: true } })
        wrapper.vm.toggeleFavorite()
        expect(wrapper.vm.currentFavorited).toBe(false)
      })
    })
  })
})

テストを実行

無事にテストが実行できました🙌

npm run test

> nuxt-app@1.0.0 test /nuxt-app
> jest --config jest.config.js spec/**/*.js

 PASS  spec/components/Favorite.spec.js (9.758s)
  components/Favorite.vue
    ✓ is a Vue instance (40ms)
    toggeleFavorite
      is not favorited.
        ✓ toggle favorited. (8ms)
      is favorited.
        ✓ toggle unfavorited. (5ms)
    favoritedColor
      is not favorited.
        ✓ return unfavorited color. (4ms)
      is favorited.
        ✓ return favorited color. (15ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        15.486s

CIでテストを実行する

さらに今回はCircleCIを使ってCI上でJestとESLintを実行するようにしてみました、設定ファイルは下記のような感じです⚙

version: 2
jobs:
  build:
    docker:
      - image: circleci/node:12
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - persist_to_workspace:
          root: .
          paths:
            - .
  lint:
    docker:
      - image: circleci/node:12
    steps:
      - attach_workspace:
          at: .
      - run: npm run lint
  test:
    docker:
      - image: circleci/node:12
    steps:
      - attach_workspace:
          at: .
      - run: npm run test
workflows:
  version: 2
  build-test-lint:
    jobs:
      - build
      - lint:
          requires:
            - build
      - test:
          requires:
            - build

Workflowを使ってJestによるテストとESLintによる静的解析が並列で走るようになっています🤖

f:id:madogiwa0124:20200209134729p:plain

発生したエラーとTips

Vueのコンポーネントをテストしてるときにハマったことを色々メモしておきます📝

[Vue warn]: Unknown custom element: <v-icon> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Vuetifyで定義されたコンポーネントが認識できていないことが原因、Vue.use(Vuetify)を実行してVuetifyを使うことを明示してあげたところ解決した。

import Vue from 'vue'
import Vuetify from 'vuetify'
Vue.use(Vuetify)

github.com

Nuxtで定義されたコンポーネントnuxt-linkを認識できていないことが原因、RouterLinkStubを使ってstubして解決した。

import { mount, RouterLinkStub } from '@vue/test-utils'
const wrapper = mount(Component, { stubs: { NuxtLink: RouterLinkStub } })

onigra.github.io

[Vue warn]: Error in render: “TypeError: Cannot read property ‘resolve’ of undefined”

前回と違って今回はtoオプションを使ってリンク先をしていたため、RouterLinkStubを使ったStubがうまく使えなかったので別の方法を使う必要がありました。

    <v-btn value="recent" to="/foods">
      <span>Recent</span>
      <v-icon>mdi-history</v-icon>
    </v-btn>

実際に行った方法は、下記のような形でLocalVueを使ってRouterオブジェクトを作成して渡してあげることで解決できました。

import Vue from 'vue'
import VueRouter from 'vue-router'
import { mount, createLocalVue } from '@vue/test-utils'

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

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

vue-test-utils.vuejs.org