node.js で動くWebアプリを作り、mocha などでユニットテストを書きたいというシチューエーションになりました。

しかし、そのWebアプリは request によって外部コンテンツを
参照しているため、なかなかテストが書きにくいです。

そこで、探し出してきた便利ライブラリが nock です。

https://github.com/pgte/nock

このライブラリを使うと、特定のHTTPリクエストを mock することが
できるようです。
下のようなテストコードが、パスします。

var request = require('request')
    , nock = require('nock')
    , assert = require('assert');

describe('http request', function() {
  describe('get request', function() {
    it('should return {"hello": "world"}', function() {
      // mocking http request
      nock('http://www.example.com')
        .get('/data')
        .reply(200, {
          hello: 'world'
        }, {
          'Content-Type': 'application/json'
        }); 

      // request
      request('http://www.example.com/data', function(err, response, body) {
        assert.equal(err, null);
        assert.equal(response.statusCode, 200);
        assert.equal(body, '{"hello":"world"}');
      }); 
    }); 
  }); 
});

冬休みの自由研究として、 angular.js を触り始めましたのでメモです。

logo

こいつが、個人的にはなかなか好みです。

  • Dependency Injection がある
  • テストの書き方とかのガイドもしっかり揃っている (英語だけど)
  • エラー時の手厚いサポート
  • RESTful な WebAPIとの親和性

というわけで、今後使っていくために、簡単なアプリを書いてみました。

https://github.com/kawahara/devnull
シンプルな一言メモ的なものです。
まだ、ACLなどを実装しておりませんので、実用なものではありません。

実際に動きを見たい場合は、node.js と mongodb を用意して、

npm install
node app.js

と言った感じで、起動し、 http://localhost:3000 などを見てください。

サーバサイドのアプリは、何を思ったのか、swig が使える設定が入っていますが、
これは最初の開発の試行錯誤の段階で使っていたものです。

実際には、テンプレーティングをいないものです。
サーバサイドのアプリが行っているのは、WebAPI の提供のみです。

今のところ以下のような、WebAPI を提供しています。

  • 投稿
    • GET /api/posts – 投稿一覧を取得する
    • POST /api/posts – 投稿する
    • GET /api/posts/:id – 投稿を取得する
    • DELETE /api/posts/:id – 投稿を削除する
  • 投稿に対するコメント
    • GET /api/posts/:postId/reply – 投稿に値するコメントを取得する
    • POST /api/posts/:postId/reply – 投稿に対してコメントする
    • DELETE /api/posts/:postId/reply/:id – 投稿に対するコメントを削除する

クライアントのアプリは、静的コンテンツである、public/index.html
angualr.js によって動作しているわけです。コントローラーのコードは、
public/devnull.js に書いています。

resource module を使うと、RESTful な WebAPI を簡単に利用することができます。

HTML側では、angular.js のパッケージに同梱されている angular-resource.min.js を
ロードしておきます。

ではサービスの定義。

var postsServices = angular.module('postsServices', ['ngResource']);
postsServices.factory('Post', ['$resource',
  function($resource) {
    // $resource オブジェクトを作成, 第2引数はデフォルトのパラメータオブジェクト
    // 第3引数はアクションを定義したオブジェクト
    return $resource('api/posts/:id', {}, {
      // 取得 (配列で取得する)
      query: {method: 'GET', isArray: true},
      // 追加は POST で
      save: {method: 'POST'},
      // 削除は DELETE で
      remove: {method: 'DELETE'}
    });
  }
]);

postsServices.factory('Reply', ['$resource',
  function($resource) {
    return $resource('api/posts/:postId/reply/:id', {}, {
      save: {method: 'POST'},
      remove: {method: 'DELETE'}
    });
  }
]);

これらのサービスを、コントローラーで使います。

var devnullApp = angular.module('devnullApp', ['postsServices']);
devnullApp.controller('postsCntl', ['$scope','Post','Reply',
  function($scope, Post, Reply) {
     // controller
     // Post や Reply リソースを利用できる
  }
])

コントローラーで Post.query() を実行すると、 GET api/posts でデータの取得が行われ、
POSTリソースのオブジェクトの配列となって利用できます。
この時、URIは、 api/posts/:id と宣言していますが、id を指定しない場合は、
省略され、 api/posts という形になるようです。

新しく Post を投稿する際は、Post リソースのオブジェクトを新しく作成し、
$save() メソッドを利用します。

var post = new Post({body: "Hello, world."});
post.$save(function(savedObject, handler) {
  // 保存後の処理
});

IDなどを別途指定する必要がある場合は、リソースのアクションメソッド
の第1引数に渡します。

post.$remove({id: "123"}, function() {
  // 削除後の処理
});

もちろん、これに対するテストを書いていきます。

まず、公式ドキュメントの通り、karma を利用します。対象のテストや実装が更新されると
自動的にテストを流してくれたりする、素晴らしいツールです。

npm install -g karma karma-jasmine karma-junit-reporter karma-chrome-launcher karma-firefox-launcher karma-script-launcher

config/karma.config.js を用意して、karma を起動します。
コンソールに出たURLを、ブラウザで見ると、テストやスクリプトが更新された際に、
全自動でテストが実行されます。

karma start config/karma.conf.js
INFO [karma]: Karma v0.10.9 server started at http://localhost:9876/

通常、WebAPIを利用したアプリのテストを書くのは面倒に思えますが、
angualr.js では http request 周りを Mock することができる、 $httpBackend を備えています。

これは、特定のリクエストが行われたかを確認したり、
そのレスポンスを自分が指定したものを返すといったことができます。

test/unit/controllersSpec.js に jasmine 用のテストケースを用意しました。

$httpBackend の基本的な使い方としては、

  1. 予測されるリクエストについて書く
  2. アクションを実行
  3. $httpBackend.flush() を実行。この時点でHTTP Request が実際に行われる状態になる。(そもそも、そのようなコールがないような状態だと、テストがコケる。)

という流れになります。

今回のアプリは、ページを開いた際に Post.query() が実行されるようなものになっているので、
GET api/posts がコールされることが予測されます。

なので、

    beforeEach(inject(function(_$httpBackend_,$rootScope, $controller) {
      $httpBackend = _$httpBackend_;

      $httpBackend.expectGET('api/posts')
        .respond(200, postsData);

      scope = $rootScope.$new();
      ctrl = $controller('postsCntl', {$scope: scope});
    }));

のように、api/posts に関しては、200と、予め用意したテストデータを返すようにします。
(これは全てのテストケースについて定義したいので、beforeEach 内に書きます。)

そのうえで、

    it('should display post list', function() {
      expect(scope.posts).toEqualData([]);
      $httpBackend.flush();
      expect(scope.posts).toEqualData(postsData);
    });

$httpBackend.flush() が実行されたあと、正しくデータが scope にセットされているか。
というのを確かめるというテストが書けます。

    it('should display a new post after the submit action', function() {
      var submitData = {
        body: "Hello World."
      };

      $httpBackend.expectPOST('api/posts')
        .respond(201, "");
      scope.post = submitData;
      scope.submit();
      $httpBackend.flush();

      expect(scope.post).toEqualData(null);
      expect(scope.posts[0]).toEqualData(submitData);
    });

投稿のテストも、同様に expectPOST() を使うことで書くことができます。

あけましておめでとうございます

2014年です。ついでに、25歳になりました。
平成になって生まれましたが、平成が四半世紀おわったわけです。
すごいですね。

というわけで、今年は、20歳から5年。今の仕事はじめて3年目ということで、
原点復帰の年にしたいと思います。

仕事面では、かねてよりやろうと思って、チマチマ準備していたリリース全自動化 (Capistrano を使う
というレベルではなくビルドから承認フローも含めた感じのもの) を実践していく感じで
諸諸やっております。

Symfony諸諸の話では、去年の末にリリースされた 2.4 を本番環境で使う予定で居ます。
hosts による、リクエストの制限機能とかがすごく使えそうです。

LTS で切り替えていこうかな、と思っていたものの、2.0 -> 2.3 が思った以上に大変だったので、
細かくバージョンアップする戦術に変えていきたいです。

あとは、「彼女ができる」とか、変なことを掲げたりもしているのが今年です。

技術ブログは不定期ながら月に4回くらいは書きたいなーということで、最近趣味で触り始めた、
angular.js のネタとかをお送りしますよ。

それでは、本年もよろしくお願い致します。