rack middlewareのテストをrspecで書いてみた

## 背景

middlewareといっても、基本はinitializeとcallの2つのメソッドだけのclass。
rackを継承してるわけでもないので、適当なアプリをmiddlewareに食わせてテストを書こうかなぁ…
と悩んでいると、いい記事を見つけた。

[Rack MiddlewareのGemに対するRspecの書き方](http://a-newcomer.com/32)

やってることは、

* callされたら適当なレスポンス返すだけのシンプルなアプリをテスト用に定義しておく
* rackアプリをこれに食わせてnewしておく
* Rack::Test::Methodsをincludeする

すると、Railsのcontrollerのテストのように、リクエストを投げて、そのレスポンスを使ってテストを書くことができる。

## spec

先日作成したrack middlewareのテストを書いてみる。
middlewareは[rack middlewareをざっくり触ってみた](http://qiita.com/ogawatti/items/06d0d822b65138bf6301)を参照。

##### callされたら適当なレスポンス返すだけのシンプルなアプリをテスト用に定義しておく

```rb:spec/support/test_application_helper.rb
module TestApplicationHelper
  extend self

  class TestApplication
    def call(env)
      code   = 200
      body   = [ "test body" ]
      header = { "Content-Type"           => "text/html;charset=utf-8",
                 "Content-Length"         => "9",
                 "X-XSS-Protection"       => "1; mode=block",
                 "X-Content-Type-Options" => "nosniff",
                 "X-Frame-Options"        => "SAMEORIGIN" }
      [ code, header, body ]
    end
  end
end
```

##### rackアプリをこれに食わせてnewしておく & Rack::Test::Methodsをincludeする

参照した記事にもあるが、appでrack_middlewareが返るようになっていれば、
HTTPリクエストを使ったテストができるようになる。

```rb:my_rack_middleware_spec.rb
describe MyRackMiddleware do
  include TestApplicationHelper
  include Rack::Test::Methods

  let(:test_app) { TestApplicationHelper::TestApplication.new }
  let(:app)      { MyRackMiddleware.new(test_app) }

  describe "GET '/hoge'" do
    it 'should return 200 OK' do
      get '/hoge'

      expect(last_response.status).to eq 200
      expect(last_response.body).to eq html
      expect(last_response.header["Content-Type"]).to eq "text/html;charset=utf-8"
      expect(last_response.header["Content-Length"].to_i).to be > 0
      expect(last_response.header["X-XSS-Protection"]).to eq "1; mode=block"
      expect(last_response.header["X-Content-Type-Options"]).to eq "nosniff"
      expect(last_response.header["X-Frame-Options"]).to eq "SAMEORIGIN"
    end
  end
end
```

## last_request & last_response

Rack::Test::Methodsでリクエストを送信すると、last_request、last_responseそれぞれに値が格納される。
上記のテストのようにこれを使ってテストを書く。

#### last_request

テスト中で最後に送信したリクエスト。

* middlewareのcallで受け取るenvがそのままリクエストに入っている
* middlewareでリクエストを上書いた場合や追加した場合はlast_requestに反映される
* bodyはrack.inputに格納されている ([別記事参照](http://qiita.com/ogawatti/items/06d0d822b65138bf6301#3-1))

```rb:last_reqeuest
last_request.class      #=> Rack::Request
last_request.env.class  #=> Hash
last_request.env        #=> 
{ "rack.version"      => [1, 2],
  "rack.input"        => #<StringIO:0x00000002a17708>,
  "rack.errors"       => #<StringIO:0x00000002a17f00>,
  "rack.multithread"  => true,
  "rack.multiprocess" => true,
  "rack.run_once"     => false,
  "REQUEST_METHOD"    => "GET",
  "SERVER_NAME"       => "example.org",
  "SERVER_PORT"       => "80",
  "QUERY_STRING"      => "",
  "PATH_INFO"         => "/hoge",
  "rack.url_scheme"   => "http",
  "HTTPS"             => "off",
  "SCRIPT_NAME"       => "",
  "CONTENT_LENGTH"    => "0",
  "rack.test"         => true,
  "REMOTE_ADDR"       => "127.0.0.1",
  "HTTP_HOST"         => "example.org",
  "HTTP_COOKIE"       => "" }
```

#### last_response

テスト中で最後に受信したレスポンス

* RackのMockReponseが返ってくる
* status, body, headersへのaccessorが付いている
* Content-Lengthはlengthでアクセスできる

```rb:last_response
last_response.class    #=> Rack::MockResponse
last_response.body     #=> "hogehoge"
last_response.errors   #=> ""
last_response.headers  #=>
{ "Content-Type"           => "text/html;charset=utf-8",
  "Content-Length"         => "8",
  "X-XSS-Protection"       => "1; mode=block",
  "X-Content-Type-Options" => "nosniff",
  "X-Frame-Options"        => "SAMEORIGIN" }
last_response.length   #=> 8
last_response.status   #=> 200
```

ちなみに、Rack::Test::Methodsでリクエストを送信前にlast_request、last_responseにアクセスするとエラーになる。

```rb
last_request   #=> Rack::Test::Error: No request yet. Request a page first.
last_response  #=> Rack::Test::Error: No response yet. Request a page first.
```

## 参考

* [Rack MiddlewareのGemに対するRspecの書き方](http://a-newcomer.com/32)