.../articles/

Nuxt.js + Jest + Codecovでフロントエンドのテストに入門した

みなさんフロントエンドのテスト、書いてますか? 弊社でもフロントエンドのテストもちゃんとやらないとなってことで、Nuxt.jsを利用したプロジェクトでJest + Codecovで、Unit Testをし、Coverageを確認できるようにしました。

タイトルの通り今更ながらですが、フロントエンドのテストに入門しました。

弊社ではNuxt.jsを利用したプロジェクトが多いので、まずはそこに JSのテストフレームワークである Jest と カバレッジ確認用に Codecov を追加しました。

以下のコマンドで必要なpackageをインストールします。

for Jest

$ yarn add -D jest vue-jest ts-jest babel-jest @vue/test-utils babel-preset-vue-app

for Codecov

$ yarn add -D codecov

その後、package.jsonにscriptを追加しておきます。
test:ciではcoverageを吐き出したいので、optionを追加しています。

  "scripts": {
    "test": "jest --config jest.config.js",
    "test:ci": "jest --config jest.config.js --coverage"
  },

今回はアプリケーションのルートディレクトリに tests ディレクトリを用意して、その中にファイルを入れています。
mock用のデータもそこに入れています。
ファイル名は変えておりますが、階層構造はこんな感じ。

rootDir
└── 
    tests
    └── unit
        ├── _mockData
        │   └── posts.json
        ├── compounds
        │   └── button
        │       └── ButtonDefault.spec.js
        └── organisms
            └── list
                └── ListButton.spec.js

あとは、jest用の設定ファイル jest.config.js を用意してやります。
Nuxtで(部分的に)tsを利用しているため、 preset: 'ts-jest', も追加しております。

module.exports = {
  preset: 'ts-jest',
  transform: {
    '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest',
    '^.+\\.ts$': '<rootDir>/node_modules/ts-jest',
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/app/$1',
    '^~/(.*)$': '<rootDir>/app/$1',
  },
  moduleFileExtensions: ['js', 'json', 'vue', 'ts'],
  testMatch: [
    '<rootDir>/tests/unit/**/*.js',
    '<rootDir>/tests/unit/**/*.ts',
  ],
  globals: {
    'ts-jest': {
      tsConfig: 'tsconfig.json',
    },
  },
  collectCoverage: false,
  collectCoverageFrom: ['<rootDir>/app/components/**/*.vue', '<rootDir>/app/pages/**/*.vue'],
  coverageDirectory: './coverage/',
  coverageReporters: ['json', 'text-lcov', 'lcov'],
}

諸々のファイル準備が終わったら、テストを書いていきましょう。

今回テストしたいファイルは、以下の components/organisms/list/ListButton.vue です。

<template>
  <ul class="list">
    <li
      v-for="(post, i) in posts"
      :key="i"
    >
      <a
        @click="$emit('clickItem', post)"
      >
        {{ post.name }}
      </a>
    </li>
    <li>
      <a
        @click="$emit('clickAdd')"
      >
        新規追加する
        <svg viewBox="0 0 24 24">
          <use xlink:href="#plus" />
        </svg>
      </a>
    </li>
  </ul>
</template>

<script>
import VueTypes from 'vue-types'

export default {
  props: {
    post: VueTypes.arrayOf(VueTypes.shape({
      name: VueTypes.string.isRequired,
      description: VueTypes.string,
    })).isRequired.loose,
  },
}
</script>

このファイルのテスト観点は

  • propsで渡されているデータ
  • click eventが正しく動作しているかどうか
    としました。
import { mount, shallowMount } from '@vue/test-utils'
import posts from '../../_mockData/posts.json'
import ListButton from '@/components/organisms/list/ListButton.vue'

describe('components/organisms/list/ListButton.vue', () => {
  const propsData = {
    list: posts.list,
  }
  test('props', () => {
    const wrapper = mount(ListButton, { propsData })
    expect(wrapper.props()).toEqual(propsData)
  })
  test('check click link event', () => {
    const wrapper = shallowMount(ListButton, { propsData })
    // 全てのa tagを取得
    const links = wrapper.findAll('a')
    links.wrappers.forEach((link, index, links) => {
      // 最後のa tagだけ、機能が異なるので、テストを分岐
      if (index === 0) {
        link.trigger('click')
        // click eventが取れているか確認する
        expect(wrapper.emitted().clickItem).toBeTruthy()
        // click eventで渡される内容が指定のものか確認する
        expect(wrapper.emitted().clickItem[0][0]).toBe(posts.list[0])
      } else if (index === (links.length - 1)) {
        link.trigger('click')
        expect(wrapper.emitted().clickAdd).toBeTruthy()
      }
    })
  })
})

あとは、

$ yarn test

とローカルで実行してやると、内容をチェックしてくれます。

ここまでやると、coverageとかもチェックしたくなりますよね。

弊社ではCircleCIを使っているのでConfigに以下を追加してやります。
(circle ciの設定画面でtokenの設定も必要ですが)これだけで、coverageをいい感じに図示してくれます!

      - run:
          name: Testing and export coverage report
          command: yarn test:ci
      - run:
          name: Codecov
          command: yarn codecov

吐き出されるのは、こんなやつとか

こんなやつとか、、、、、、、、(焼け野原かな。。。。?😇😇😇)

と、まあJest + Codecovでテストとカバレッジの確認ができるようになりました!
この焼け野原を見たら、テストする気にもなりますよね!!!!!!!!!!!!!!
カバレッジをcircleciのartifactsにアップロードしても悲しいかな誰も見ないので、PRなどで目につくようになるのもメリットだなと思いました!
皆さんも是非導入してみてください😎

今後は少しずつ書いていきながらテストの知見をためて、またシェアできればと思います。

.../articles/

Articles

記事

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

AWS AmplifyにmonorepoのNext.js(App Router)をデプロイする

monorepo管理しているNext.jsをAmplifyにデプロイしようとした際にいくつか躓く内容があったのでまとめておきます。

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

リモートワーク・オンライン会議でも、スムーズに制作を進めるために大切なこと[資料編]

コロナ禍の影響により、リモートワークの導入をおこなっている制作会社も多く、実際に弊社でも導入しています。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトデザインを作るために。参考にしたいおしゃれな事例の探し方。

売れるECサイトのデザインは、「この形式」という決まりはありません。ECサイトで売り上げを上げるなら、しっかりとしたコンセプトと、コンセプトを決定するまでのリサーチが必要です。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

制作会社の考える、業務効率化ツールのおすすめ。個人でも使いやすいサービスなど。

新型コロナウイルス感染拡大の影響で、リモートワークが主流になり、弊社でも週のほとんどは各自宅で作業をしています。

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaでデザインのコミット履歴を残せるプラグイン【Thought Recorder】をリリースしました

Figmaを利用するWebデザイナーの助けになれると嬉しいです。使い方は本記事をご覧ください。

ECの構築方法、おすすめのECサービス。

ECの構築方法、おすすめのECサービス。

ファッションや家電、スーパーの買い物でさえもECサイトを利用することが当たり前になりました。加えて新型コロナウイルスの影響もあり、弊社にも「どんなプラットフォームを利用したら良いか」「どれくらいコストがかかるのか」などECに関するさまざまなご相談を頂きます。

FastAPIのスキーマクラスをOpenAPIから生成する方法

FastAPIのスキーマクラスをOpenAPIから生成する方法

PythonでAPIを構築する要件があり、フレームワークに比較的モダンなFastAPIを採用しました。FastAPIはバックエンドの開発を行えば自動でOepnApi定義を生成する機能が備わっていますが、今回はこれを使わず、事前に用意したOepnApi定義からFastAPIで利用するスキーマクラスを生成する方法を紹介します。

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel 日本一解りやすい全文検索のマイグレーション記載方法解説

Laravel + MySQLで全文検索を実装する

GiFT1号目新卒デザイナーの2021年振り返り

GiFT1号目新卒デザイナーの2021年振り返り

いつの間に、年末ですね。入社してもう、9ヶ月も立っていたようです。2021年の振り返りを記事にしました。

TimesclaeDBのデータ圧縮に関して

TimesclaeDBのデータ圧縮に関して

TimescaleDBはデータベース内の一部のテーブルを時系列データとして扱えるPostgreSQLの拡張です。PostgreSQLの機能拡張なので非常に手軽に導入できます。今回はこのTimesaceDBの圧縮について調べたので備忘録として書き綴りました。

すべての記事

お問い合わせ