AngularJSでバックエンドの疑似APIサーバ(スタブ)を構築する


WEBサービスを実現する場合、フロントエンドからバックエンドのAPIサーバにアクセスすることが多いと思いますが、バックエンド側がまだ開発途中であったりすると、代替としてNode.js等でスタブのアプリケーションを作ったり、または固定のJSONファイルを作ったりと、割と手間がかかってしまいます。

今回、ためしに AngularJS の [ngMockE2E](https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend) を利用して、擬似的なAPIサーバを構築してみたので、その方法を紹介します。

# 前提

- Yeomanで構築したAngularJS開発環境が前提です。
	- `$ yo angular --minsafe`
	- そうすると、次のように ngMockライブラリが最初から入ってます。

		![スクリーンショット 2014-05-13 14.38.32.png](https://qiita-image-store.s3.amazonaws.com/0/35733/80150ae8-dac4-6539-b3df-9fa21398b675.png "スクリーンショット 2014-05-13 14.38.32.png")

	- 参考：[Yeoman で AngularJS ＆ UI Bootstrap の開発環境構築](http://qiita.com/hkusu/items/7d748b55ba73cc8a3675)

- 別途 ngMockライブラリをインストール場合も、Bower を使えば簡単にできます。
	- `$ bower install angular-mocks`

# ngMockを使う準備

### ①index.htmlからngMockのJSファイルをロード 

```html:app/index.html
<script src="bower_components/angular-mocks/angular-mocks.js"></script>
```

### ②ルートモジュールに`ngMockE2E`を追加

```diff:app/scripts/app.js
'use strict';

angular.module('ngMockTestApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'ngRoute',
+  'ngMockE2E'
])
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .otherwise({
        redirectTo: '/'
      });
  }]);
```

これで準備は完了です。

# 疑似APIサーバの処理を書く

- 先程と同じ `app.js` に、`.run` 以降を追記してみます。
	- 面倒なのでこうしてますが、本当はファイルを分けた方がいいです。

```javascript:app/scripts/app.js
'use strict';

angular.module('ngMockTestApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'ngRoute',
  'ngMockE2E'
])
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .otherwise({
        redirectTo: '/'
      });
  }])
  .run(['$httpBackend', function ($httpBackend) {

    $httpBackend.whenGET(/^views\//).passThrough();

    var sample = [
      {
        "id": "1",
        "name": "山田"
      },
      {
        "id": "2",
        "name": "鈴木"
      }
    ];

    $httpBackend.whenGET('data/sample.json?hoge=1').respond(200, sample, {});

    $httpBackend.whenGET('data/sample.json?hoge=2').respond(function(method, url, data, headers) {
            return [200, sample, {}];
    });

  }])
;
```

# 動作確認

ここは何でもいいのですが、APIを叩く処理を書きます。

まず、APIにアクセスするFactoryを作って、

```javascript:app/scripts/services/data.js
'use strict';

angular.module('ngMockTestApp')
  .factory('JsonData', function ($http) {

    return {

      getSampleData: function () {

        return $http.get('data/sample.json?hoge=1')

          .success(function(data, status, headers, config) {

            return data;

          });

      }

    }

  })
;
```

Controllerから呼び出し、

```javascript:app/scripts/controllers/main.js
'use strict';

angular.module('ngMockTestApp')
  .controller('MainCtrl', ['$scope', 'JsonData', function ($scope, JsonData) {

    JsonData.getSampleData().then(function(res){

      $scope.items = res.data;

    });

  }])
;
```

Viewで描写します。

```html:app/views/main.html
<!-- 対応する Controller は app.js の $routeProvider で定義済み -->
<li ng-repeat="item in items">
  {{item.id}} - {{item.name}}
</li>
```

結果はこんな感じ。表示されました！

![スクリーンショット 2014-05-13 15.06.07.png](https://qiita-image-store.s3.amazonaws.com/0/35733/abb62b22-f5d6-e8a7-059f-f53fe9a34b80.png "スクリーンショット 2014-05-13 15.06.07.png")

# 疑似APIサーバの処理をみてみる

```javascript
  .run(['$httpBackend', function ($httpBackend) {

    $httpBackend.whenGET(/^views\//).passThrough();

    var sample = [
      {
        "id": "1",
        "name": "山田"
      },
      {
        "id": "2",
        "name": "鈴木"
      }
    ];

    $httpBackend.whenGET('data/sample.json?hoge=1').respond(200, sample, {});

    $httpBackend.whenGET('data/sample.json?hoge=2').respond(function(method, url, data, headers) {
            return [200, sample, {}];
    });

  }])
```

全ての HTTPアクセスがこのロジックを経由します。
で、ここに全てのHTTPアクセスについて定義しないとエラーになるので、注意が必要です。
（正規表現はつかえます。）

`.passThrough()`は何もしない、という意味です。テンプレートのアクセスもHTTP経由なので、「URLが `views/` で始まる場合は何もしない」というふうに記載しています。

`whenGET` でURLを定義し、対応するレスポンスを `.respond` に記載します。今回は２パターンを記載してみました。

上のパターンは、単純に用意されたJSONデータをレスポンンスしています。

下のパターンは、JavaScriptでいろいろ処理させてからレスポンンスさせるパターンです。
この例だと、結局JSONをレスポンスしているだけですが、判定処理とか動的なJSONファイルの生成処理がここに書けるわけです。

# ほか

- 詳しくは AngularJS のドキュメントをご覧ください。
	- https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend

- 今回つくったソースはGitHubにおいてあります。
	- https://github.com/hkusu/AngularJS_ngMock_demo