Help us understand the problem. What is going on with this article?

Ionicにユニットテスト(Jest)の環境を作る

More than 1 year has passed since last update.

Ionic CLIでプロジェクトを作るとなぜかユニットテスト環境が用意されません。実はこれ昔からそうで、いつか用意されるだろうと思って2年ぐらい経ちましたが全くそういう気配がありません。
じゃあAngular CLIで用意されるKarmaの環境を持ち込もうと思ってもボイラープレートがやたら多くて勘弁してくれよとなります。(個人の感想)

なので色々調べたらJestの環境がさくっと作れるっぽいのでやってみました。

今回のリポジトリは https://github.com/ovrmrw/ionic-super-template-pwa-jest-prettier です。(余談ですがPrettierの設定も組み込んでいます)

多分早ければ1分くらいで環境作れます。

Ionic CLIでプロジェクトを作る

ionic startコマンドでプロジェクトを作ります。

$ ionic start myApp super

これでSuper Starterというてんこ盛りのテンプレートからプロジェクトが作成されます。

Jestをインストールする

$ cd myApp
$ npm install -D jest-preset-angular jest @types/jest

このjest-preset-angularというやつがいい感じに色々やってくれます。

Jestの設定ファイルを作る

プロジェクトのルートディレクトリに作ります。おまじないみたいなものです。

jest.config.js
module.exports = {
  preset: 'jest-preset-angular',
  roots: ['src'],
  testRegex: '\\.spec\\.ts$',
  setupTestFrameworkScriptFile: '<rootDir>/src/setupJest.ts',
  transformIgnorePatterns: ['node_modules/(?!@ngrx|@ionic-native|@ionic)']
};

Jestの実行時に読み込むファイルを作る

これらのファイルはsrcディレクトリの直下に作ります。おまじないみたいなものです。

src/setupJest.ts
import 'jest-preset-angular';
import './jestGlobalMocks'; // browser mocks globally available for every test
src/jestGlobalMocks.ts
const mock = () => {
  let storage = {};
  return {
    getItem: key => (key in storage ? storage[key] : null),
    setItem: (key, value) => (storage[key] = value || ''),
    removeItem: key => delete storage[key],
    clear: () => (storage = {})
  };
};

Object.defineProperty(window, 'localStorage', { value: mock() });
Object.defineProperty(window, 'sessionStorage', { value: mock() });
Object.defineProperty(window, 'getComputedStyle', {
  value: () => ['-webkit-appearance']
});
src/tsconfig.spec.json
{
  "extends": "../tsconfig.json",
  "include": [
    "**/*.spec.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

package.jsonを編集する

テストを実行するためのコマンドを用意します。

package.json
  (省略)
  "scripts": {
    (省略)
    "test": "jest",
    "test:w": "jest --watch",
    "test:ci": "jest --runInBand"
  },
  (省略)

環境構築終わり

Jestによるユニットテストの環境構築はこれで全てです。
コードは全てコピペでOKなので手間と言えるほどの作業は一切ありません。

※おまじないファイルを一括生成するスクリプトも用意しました。
https://qiita.com/ovrmrw/items/a25a229da02a6dbe2603#%E8%BF%BD%E8%A8%98

Serviceのテストを書いてみた

simple.service.spec.ts より

simple.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { SimpleService } from './simple.service';

describe('SimpleService', () => {
  beforeEach(() =>
    TestBed.configureTestingModule({
      providers: [SimpleService]
    })
  );

  describe('with injection', () => {
    let simpleService: SimpleService;

    beforeEach(() => {
      simpleService = TestBed.get(SimpleService);
    });

    it('add', () => {
      const expected = 3;
      const actual = simpleService.add(1, 2);
      expect(actual).toBe(expected);
    });

    it('subtract', () => {
      const expected = -1;
      const actual = simpleService.subtract(1, 2);
      expect(actual).toBe(expected);
    });
  });
});

add()subtract()という関数を持つServiceの簡単なテストです。

Component(Page)のテストを書いてみた

welcome.spec.ts より

welcome.spec.ts
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { IonicModule, NavController } from 'ionic-angular';
import { WelcomePage } from './welcome';

const mockNavController = {
  push: jest.fn()
};

describe('WelcomePage', () => {
  beforeEach(() =>
    TestBed.configureTestingModule({
      declarations: [WelcomePage],
      imports: [IonicModule.forRoot(WelcomePage), TranslateModule.forRoot()],
      providers: [{ provide: NavController, useValue: mockNavController }],
      schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents()
  );

  describe('with injection', () => {
    let fixture: ComponentFixture<WelcomePage>;
    let welcomePage: WelcomePage;

    beforeEach(() => {
      fixture = TestBed.createComponent(WelcomePage);
      welcomePage = fixture.componentInstance;
    });

    afterEach(() => {
      jest.resetAllMocks();
    });

    it('should be created.', () => {
      expect(welcomePage).toBeTruthy();
    });

    it('when login function is called.', () => {
      welcomePage.login();
      expect(mockNavController.push.mock.calls.length).toBe(1);
      expect(mockNavController.push.mock.calls[0]).toEqual(['LoginPage']);
    });

    it('when signup function is called.', () => {
      welcomePage.signup();
      expect(mockNavController.push.mock.calls.length).toBe(1);
      expect(mockNavController.push.mock.calls[0]).toEqual(['SignupPage']);
    });
  });
});

ComponentのテストはServiceより記述量が多くなりますが、やっていることは

  • インジェクトしているService(NavController)をモックする。
  • DIコンテナからComponentのインスタンスを取得してテストする。
    • インスタンスが生成されていることの確認。
    • NavContollerのpush()が指定した引数を伴って呼ばれていることの確認。

これだけです。

まとめ

JestでComponentのテストもできることがわかりました。
環境構築はあっという間にできるのでIonic CLIがやってくれるとありがたいのですが。

※全体的に参考にしたのは https://github.com/datencia/ionic2-jest-example です。

追記

おまじないファイルを一括生成するスクリプトを用意しました。

https://gist.github.com/ovrmrw/57f8e02f5776656825f8db8867f03578

上記のJSファイルのコードをコピーして、プロジェクトのルートディレクトリで

$ node
> (←入力を受け付ける状態になる)

ここでコードを貼り付けるとスクリプトが実行されてファイルが生成されます。楽ちんですね。

ovrmrw
ちきさんです。ただのWebエンジニアです。
http://overmorrow.hatenablog.com/
opt
"INNOVATION AGENCY" を標榜するインターネット広告代理店。エンジニア組織 "Opt Techonologies" を中心にアドテクetc...に取り組んでいます。
https://opt-technologies.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした