BETA

Guardによる認可処理(単体テスト編)

投稿日:2019-01-14
最終更新:2019-01-14

こんにちは、たーせるです。 風邪を引いて寝込みました。

本日の記事は、おふとんからお送りいたします。

はじめに

この記事は、先日書いた Guard の解説記事のおまけです。

前回は、Guard (src/app/auth.guard.ts) のコーディングについてお話ししました。

Guard は、ターミナルでプロジェクトのルートディレクトリに cd し、以下のコマンドを打ち込むことで自動的に生成されるのでしたね。

$ ng guard Auth

しかし、上記のコマンドを叩くと、本体 (src/app/auth.guard.ts) のほかにもう一つ src/app/auth.guard.spec.ts というファイルが同時に生成されます。

末尾が spec.ts になっているファイルは、単体テストコード(以下、テストコード)を書くためのファイルです。

そこで今回は、前回作成した Guard のテストコードの書き方についてサンプルを交えて見ていくことにしましょう。

前提知識

  • 言語を問わず、単体テスト(自動テスト)について、基礎的な理解がある
  • Angular の DIProvider について、基礎的な理解がある

単体テストについて

この記事における単体テストもしくはユニットテストとは、アプリケーションを構成するプログラムの最小単位ごとに実施するテストを指します。

念のために付け加えておくと、「ユーザの操作シナリオを手動で実行し、その結果が期待通りかどうかを目視で照合する行為」のことではありません。

Jasmine について

Angular では、標準で Jasmine というテスティングフレームワークを利用できます。

Jasmine については、Qiita にあるこちらの記事が詳しいです。

例題

前回のコードを再掲します。 今回は、このコードに対してテストを書きます。

Guard のサンプルコード

src/app/auth.guard.ts

import { Injectable } from '@angular/core';  
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';  
import { Observable } from 'rxjs';  

@Injectable({  
  providedIn: 'root'  
})  
export class AuthGuard implements CanActivate {  

  constructor(private router: Router) { }  

  canActivate(  
    next: ActivatedRouteSnapshot,  
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {  

    // クエリパラメータの id が '1' の場合のみ認可。  
    // それ以外は、エラーページに強制遷移。  
    const id = next.queryParamMap.get('id');  
    if (id !== '1') {  
      this.router.navigate(['/error']);  
      return false;  
    }  
    return true;  
  }  
}

この Guard の挙動の説明については前回の記事をご参照いただくとして、少しコードを眺めてみましょう。

Router サービスが DI (依存性注入)され、さらに、内部ロジックは ActivatedRouteSnapshot に依存した実装がなされている点が少々厄介です。

テストの対象はあくまで AuthGuard クラスですので、テストコードを書く際には、それら無関係な外部サービスをことごとく切り離す(全てダミー実装のスタブに置き換える)必要があります。

Guard のテストコード

ここでは、以下 2 点を検証するコードを書いてみましょう。

  1. ActivatedRouteSnapshotqueryParamMap の内容に応じて認可判定を行っていること
    • クエリパラメータの id1 の場合、canActivate メソッドは true を返すこと(認可)
    • クエリパラメータの id1 以外(例えば null)の場合、canActivate メソッドは false を返すこと(否認)
  2. 否認の場合は Routernavigate に正しいパラメータ(/error)を引き渡していること

src/app/auth.guard.spec.ts

import { TestBed, inject } from '@angular/core/testing';  
import { AuthGuard } from './auth.guard';  
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';  

describe('AuthGuard', () => {  
  // クエリパラメータのスタブ  
  const queryParams = { id: null };  

  // ルータのスタブ  
  const router = jasmine.createSpyObj('router', ['navigate']);  

  beforeEach(() => {  
    queryParams.id = null;  
    TestBed.configureTestingModule({  
      providers: [  
        AuthGuard,  
        {  
          provide: ActivatedRouteSnapshot,  
          useValue: { queryParamMap: { get: () => queryParams.id } }  
        },  
        {  
          provide: Router,  
          useValue: router  
        }  
      ]  
    });  
  });  

  it('クエリパラメータの id が 1 のとき、認可',  
    inject([AuthGuard, ActivatedRouteSnapshot], (guard: AuthGuard, ars: ActivatedRouteSnapshot) => {  
    queryParams.id = '1';  
    expect(guard.canActivate(ars, <RouterStateSnapshot>{})).toBe(true);  
  }));  

  it('クエリパラメータの id が null のとき、error に飛ばす', inject([AuthGuard, ActivatedRouteSnapshot], (guard: AuthGuard, ars: ActivatedRouteSnapshot) => {  
    queryParams.id = null;  
    expect(guard.canActivate(ars, <RouterStateSnapshot>{})).toBe(false);  
    expect(router.navigate).toHaveBeenCalledWith(['/error']);  
  }));  
});

ここまで書けたら、ターミナルに以下のコマンドを打ち込みます。

$ ng test

テストが実行され、どちらも SUCCESS になるはずです。

めでたし。

まとめ

正直なところ、Angular のテストコードは書くのがやたら面倒なので、ついサボりたくなります。

プロダクトコードの実装工数よりもテストコードの作成工数の方が大きいことも多々ありますし、いろいろ差し迫ってくると「いま明らかに正常動作しているもののためにテストコードを書くなんてアホらしい」という気持ちに駆られることもあります。

よい子のみなさんは、決してそんな邪な心を持たずピュアでクリスタルな心を持ってテストを書いてください。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@tercelの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう