webmock使ってみた

# [webmock](https://github.com/bblimke/webmock)とは

HTTPリクエストをstubするgemです。
Net::HTTPだけでなく、TCPSocketを使った場合もstubしてくれます。

以前、[sham_rack使ってみた](http://qiita.com/ogawatti/items/9cb87c3f6228e209c502)という記事を書きましたが、
sham_rackはNet::HTTPのstubしかしてくれないのです。
TCPSocketをstubしてくれるgemを探していたところ、このgemに出会いました。

# 何ができるか

HTTPリクエストをリクエストホストに対して投げる前にフックして、あたかも処理されたようにレスポンスを返すことができる。
つまり、StubやMockのようなことができる。
しかも、Net::HTTPだけでなくTCPSocketも対応してくれる。

=> HTTPClientやExconもstubできることに！

有名なgemなので、いろんな方が記事にされていたりしますが、テスト中のリクエストのstubに利用するケースが多いようです。

# 使い方

(テストでの使い方は記事にされている方がたくさんいるのでそちらを参照してください)

基本的には、WebMock::APIを該当クラスにincludeすることでstubを登録できるようになります。
しかし、これだけだと、stubを登録しないとエラーになりますので注意してください。

### stubを登録せずにリクエストするとエラーになる

```rb
require 'webmock'
include WebMock::API
res = Net::HTTP.get("www.example.com", "/")  #=> WebMock::NetConnectNotAllowedError

__END__

具体的には以下のようなエラーメッセージが出力される。

/home/aries/.rvm/gems/ruby-2.1.5@ironna/gems/webmock-1.21.0/lib/webmock/http_lib_adapters/net_http.rb:114:in `request': Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/ with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'} (WebMock::NetConnectNotAllowedError)

You can stub this request with the following snippet:

stub_request(:get, "http://www.example.com/").
  with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Ruby'}).
  to_return(:status => 200, :body => "", :headers => {})
```

### stub_request (stubするリクエストの登録)

stub_requestで登録する。
第一引数にメソッド名(:anyにすると何でも)、第二引数にホスト名を指定する。

```rb
require 'net/http'
require 'webmock'
include WebMock::API

stub_request(:any, "www.example.com").to_return(
  :body => "test ok",
  :status => 200,
  :headers => { 'Content-Length' => 7 }
)

res = Net::HTTP.get("www.example.com", "/")
p res  #=> "abc"
```

### allow_net_connect! (stub登録したホスト以外へのアクセスは許可する)

```rb
require 'webmock'
include WebMock::API

WebMock.allow_net_connect!

stub_request(:any, "www.example.com").to_return(
  :body => "test ok",
  :status => 200,
  :headers => { 'Content-Length' => 7 }
)

http = HTTPClient.new
res = http.get "http://www.example.com/"
p res.status  #=> 200
p res.body    #=> "test_ok"

http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status  #=> 200
p res.body    #=> GoogleのトップページのHTML
```


### enable!/disable! (WebMockのon/off切り替え)

この機能が一番使いたかったやつ!!

```rb
require 'httpclient'
require 'webmock'
include WebMock::API

## includeしてstub_requestした時点でWebMockは有効になっている

stub_request(:any, "www.google.co.jp").to_return(
 :body => "mock response",
 :status => 200,
 :headers => { 'Content-Length' => 13 }
)
http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status       #=> 200
p res.body[0..30]  #=> "mock response"

WebMock.disable!  #=> 無効化
http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status  #=> 200
p res.body    #=> Googleのトップページ

WebMock.enable!  #=> 有効化
http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status  #=> 200
p res.body    #=> "mock response"
```

### reset! (登録したstubのリセット)

```rb
require 'httpclient'

require 'webmock'
include WebMock::API

WebMock.allow_net_connect!
stub_request(:any, "www.google.co.jp").to_return(
 :body => "mock response",
 :status => 200,
 :headers => { 'Content-Length' => 13 }
)
http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status  #=> 200
p res.body    #=> "mock response"

WebMock.reset!  #=> stub_requestで登録したStubがリセットされる
http = HTTPClient.new
res = http.get "http://www.google.co.jp/"
p res.status  #=> 200
p res.body    #=> Googleのトップページ
```

### stub_request().to_rack (rackミドルウェアを登録することもできる)

FbGraphを使った、GraphAPIへの/meリクエストをWebMockでmockしてみた。

```rb
require 'fb_graph'
require 'dotenv'
require 'nenv'
require 'webmock'
include WebMock::API

WebMock.allow_net_connect!
Dotenv.load

class MyRackApp
  def call(env)
    [ 500, {}, [ "{}" ] ]
  end
end

class MyMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)
    if request.path == "/me"
      test_data = { "name" => "Test User" }.to_json
      [ 200, { "Content-Type" => "application/json" }, [ test_data ] ]
    else
      @app.call(env)
    end
  end
end

app = MyRackApp.new
app = MyMiddleware.new(app)
stub_request(:any, /graph\.facebook\.com/).to_rack(app)

user = FbGraph::User.me(Nenv.access_token).fetch
p user.name  #=> "Test User"
```