LoginSignup
8
9

More than 5 years have passed since last update.

TypeScript + Angular1系プロジェクトにユニットテストを書く

Last updated at Posted at 2016-09-23

Angular1系で書かれてるプロダクトにテストを用意するにあたって環境の構築、実際のテストコードの作成など、色々調べたのでまとめていこうと思います。

前提環境

  • TypeScript(v1.8, 含テストコード)
  • AngularJS(v1.4)
  • karma+mocha+power-assert

コードはイマドキのrequireからの依存解決してbundleするスタイルではなく、TypeScriptのnamespaceでスコープを区切ってtsc --outFile dist/bundle.jsのようにconcatした単一ファイルを吐き出す形です。

環境を再現したリポジトリ:https://github.com/sisisin-sandbox/ngts/tree/master/namespace-style

最低限karmaを動かせるようにする

色々ハマりましたが、最終的にはこちらの記事を参考にしつつ、

  • プロダクトコードはconcat済のものを参照(karma.conf.jsfilesにはdist/bundle.jsを読ませる)
  • テストコードはkarma-browserifyを利用し、browserify + tsify + espowerifyでts -> espoweredなjsにして実行

という形に落ち着きました。
これで、プロダクトコードの型を参照しつつテストコードを書き、アサーションエラー時にはpower-assertの詳細なエラーが確認できます。

Directiveのテストをkarmaで動かす

DirectiveにてtemplateUrlを使ってhtmlを参照していると、下記のようなエラーが表示され、テストが落ちます。

    Error: Unexpected request: GET /views/appHelloDirective.html
    No more request expected
        at $httpBackend (/var/folders/0q/d6gvmfsn7gv849kw__r2py_00000gp/T/node_modules/angular-mocks/angular-mocks.js:1418:0 <- /var/folders/0q/d6gvmfsn7gv849kw__r2py_00000gp/T/cd8d66ce58eb2b84e246e72fab23a78f.browserify:1727:9)
        at sendReq (node_modules/angular/angular.js:11776:9)
        at serverRequest (node_modules/angular/angular.js:11571:16)
        at processQueue (node_modules/angular/angular.js:16383:28)
        at node_modules/angular/angular.js:16399:27
        at Scope.$eval (node_modules/angular/angular.js:17682:28)
        at Scope.$digest (node_modules/angular/angular.js:17495:31)
        at Context.<anonymous> (/var/folders/0q/d6gvmfsn7gv849kw__r2py_00000gp/T/test/directive-spec.ts:25:10 <- /var/folders/0q/d6gvmfsn7gv849kw__r2py_00000gp/T/cd8d66ce58eb2b84e246e72fab23a78f.browserify:17408:15)

これを回避するために、karma-ng-html2js-preprocessorというパッケージを使います。
このパッケージはAngularのtemplateとして書かれたhtmlファイルをjsに変換し、karmaで実行できるようによしなにしてくれます。
また、templateUrlのファイルパス解決についてのオプションも用意されており、実行時のパスが異なるディレクトリになっている場合でも柔軟に対応できます。

テストを書くときの方針

全体方針

  • Angularに依存している部分はanuglar-mocksでモックしつつテストする
    • Directive,$scope,$httpなど
  • DI対象が多い場合やネストしてる場合もangular-mocksを利用してやる
  • それ以外はなるべくangular-mocksを利用しないでjs(ts)オンリーでテストを書く
    • 薄いController$httpを利用しないService,Factoryなど
  • テスト用のデータは関数で取得するようにする
    • 例:const data1 = () => [1, 2, 3];
    • 生のオブジェクトや配列を$scopeなどに渡すとAngularの方でプロパティ生やされたりするので($$hashKeyなど)

Directive

  • 極力attributeから渡された値がControllerにbindされていることを確認するのみに留める
  • link関数などで盛大にロジックが書かれてしまっている場合
    • 一旦ロジックに対してもテストを書く
    • しかる後にロジックをテストとともにControllerに分離してbindToControllerに書き直し、link関数を撲滅する

基本的に、@armorik83さんのモダンプラクティス@laco0416さんのAngularJS老化チェックの書き方に則っていればテスタビリティも確保できると思います。

課題(というかまだ見えてないこと)

  • compilelink関数内で動的に出力するtemplateを差し替えていたりする場合のテストをどうするか

Controller

  • $scopeなどに依存してない部分は普通にテスト書く
  • $scope.$watchなどに依存している場合
    • $watchに登録したコールバック関数の処理が単純なら振る舞いをそのままテストする
    • 複雑な場合は、コールバック関数をプロパティなどにして、$watchの発火のテストとコールバック関数についてのテストを分割する(この辺参照)
      • ここでarrow function使っておかないとthisの扱いで爆死するので注意
      • そもそもそんな複雑なことやるなという話
    • テストを書いたら、$scope依存は消せそうか検討して極力消す。
  • $scopeのプロパティを利用していた場合
    • 全てControllerのプロパティへ移植する
    • Directiveに紐づいていた場合はbindToControllerを利用

Service,Factory

  • $http, $resoucesに依存している場合
    • $httpBackendを利用してAPIレスポンスをモックしてテスト
  • それ以外は普通にテスト書くだけ(多分

Filter

ただの関数のはずなので特別なことあんまりする必要ないと思ってます

おわりに

ということでTypeScript + Angular1系環境でユニットテスト環境の構築と、テストコードの作成方針を簡単にまとめました。
今触っているプロジェクトベースで書いているので網羅的にはなっていないと思いますが少しでも参考になればと思います。

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9