featureスペックでクライアント（JavaScript）側から呼び出すRequestをモックする

Shinosaka.rb Advent Calendar 1日目の記事です。


#はじめに
戯言です。本題を見たい人は次へ読み進めることをオススメします。
昨今フロントエンドのフレームワークないし、ライブラリの発展によりフロントエンドとバックエンド別々にテストを実行することが珍しくないと思います。ですが、最終的にはE2Eテストも"したい"、"やらないといけない"と考えた場合に厄介な問題が出てきます。
今回はその問題の１つである外部サイト（外部API）呼び出しのモックについて書きたいと思います。

#前提条件
##システム構成
まずはシステム構成で以下のような方を対象とした記事です。
![image](https://qiita-image-store.s3.amazonaws.com/0/70363/95026538-b51f-b3f2-b242-7605a4c909aa.png)
SPAで構成する場合はこんなことが往々に発生するのではないでしょうか。（サーバはRailsでなくても可）
この構成時のE2Eテストを実施する場合に、実際に外部APIを叩いてしまっては困ります。サーバ側から外部APIに投げる場合がある場合は[webmock](https://github.com/bblimke/webmock)を使用すればよいのですが、今回はそれでは不可能です。そこで[puffing-billy](https://github.com/oesmith/puffing-billy)というAPI呼出をモック可能なJavaScriptDriverを使用します。

##主な使用フレームワーク（ライブラリ）
Rails:5.0.0.1
capybara:2.10.1
puffing-billy:0.9.1
Reactjs:15.4.1

#サンプルプログラム
今回はシステム構成で紹介したプログラムとなるようなサンプルソースを[こちら](https://github.com/chimame/puffing_billy_example)に用意しました。以下に軽く解説をしておきます。

##テスト対象の確認
まずはテストするコードを一部抜粋します。

```js:frontend/js/containers/Sample/index.js
import Sample from '../../components/Sample'

class App extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    const { data, actions } = this.props
    return (
      <Sample {...data} onFetchData={actions.getSamples} />
    )
  }
}
```

まずメインとなるSampleコンテナからSampleコンポーネントへ`getSamples`というアクションを渡しています。

```js:frontend/js/components/Sample/index.js
import React, { Component } from 'react'

const Sample = (props) => {
  return(
    <div>
      {props.samples.map((item, i) => <p key={i}>{item}</p>)}
      <button onClick={() => {props.onFetchData()}}>fetch data</button>
    </div>
  )
}

export default Sample
```

Sampleコンポーネントへ渡された`getSamples`はボタンをクリックすると動作するようにしています。

```js:frontend/js/actions/sample.js
import { createAction } from 'redux-actions'
import { exampleApi } from '../apis/example'

export const GET_SAMPLES = 'GET_SAMPLES'

export const getSamples = createAction(GET_SAMPLES, exampleApi)
```

実際のaction（creater）は`exampleApi`というファンクションを実行しています。


```js:frontend/js/apis/example.js
import request from 'superagent'

export function exampleApi() {
  return request.get('http://example.com/api')
    .then((res) => res.body)
}
```

`exampleApi`のファンクションは`http://example.com/api`というURIをGETで取得しています。


```js:frontend/js/reducers/sample.js
import { handleActions } from 'redux-actions'

export const initialState = {count: 0}

const reducerMap = {
  GET_SAMPLES(state, action) {
    return {...state, samples: action.payload.test}
  },
}

export default handleActions(reducerMap, initialState)
```

最後に取得したAPIのデータをstateにしています。

**コードを見ればわかる通り`http://example.com/api`なんていうAPIは存在しないものを叩こうとしています**

#APIモックテスト
それでは実際にpuffing-billyの設定から使用方法を解説します。

##インストール
まずは以下のようにGemfileに追加して`bundle install`します。

```rb:Gemfile
group :development, :test do
  gem 'rspec-rails'
end

group :test do
  gem 'capybara'
  gem 'selenium-webdriver'
  gem 'puffing-billy'
end
```

ちょっと注意なのですが、この`puffing-billy`ですが、この記事を書いていて気づいたのですが、`listen`のgemと一緒に動かすと動かないので注意が必要です。（なので`listen`は`:development`なので`:test`に入れてます）

##テスト環境設定
インストールしたgemにRSpecが入っているのでまずはそれのgenerateで必要ファイルを生成します。

```
$ bin/rails g rspec:install
```

次に生成された`rails_helper.rb`に以下の設定を足します。

```rb
require 'capybara/rspec'
require 'billy/capybara/rspec'
require 'selenium-webdriver'
Capybara.server_port = 3100
Capybara.app_host = "http://127.0.0.1:3100/"
=begin
Capybara.register_driver :selenium_chrome do |app|
  opts = { browser: :chrome }
  opts[:switches] = ["--no-proxy-server"]
#  opts[:switches] = ["--proxy-server=proxy:3128"]
  Capybara::Selenium::Driver.new app, opts
end
Capybara.default_driver = :selenium_chrome
=end
Capybara.default_driver = :selenium_chrome_billy
ActionController::Base.asset_host = Capybara.app_host
```

今回は動作が分かるようにわざとSeleniumを入れています。また、firefoxだと面倒な問題が発生するので手間ですが、chromeで動かすようにしています。
**chromedriverが必要なのでそれはサイトにいって落としてきて下さい**

##テストコード
では最後にテストコードを見てみます。

```rb
require 'rails_helper'

feature 'Samples spec' do
  scenario 'example.comが2件のデータを返して、表示されること' do
    visit '/samples'
    proxy.stub("http://example.com/api").and_return(
      headers: { 'Access-Control-Allow-Origin'  => '*' },
      json:    {test: [1,2]},
      code:    200,
    )
    click_button 'fetch data'
    expect(all('p').count).to eq 2
  end

  scenario 'example.comが3件のデータを返して、表示されること' do
    visit '/samples'
    proxy.stub("http://example.com/api").and_return(
      headers: { 'Access-Control-Allow-Origin'  => '*' },
      json:    {test: ["a","b","c"]},
      code:    200,
    )
    click_button 'fetch data'
    expect(all('p').count).to eq 3
  end
end
```

注目してほしいのは`proxy.stub`の2箇所です。テストするコードにも書かれていましたが、`http://example.com/api`のリクエストモックをここで作成しています。これでリクエストがモックされて、設定したJSONが返ってくるようになっています。
サンプルコードをcloneして`bundle exec rspec`を動かせば正常にテストが完了します。
実際に動かした場合は以下のように動作するはずです。
![1480254309ntwPInEoaOCaygC1480254307.gif](https://qiita-image-store.s3.amazonaws.com/0/70363/d3ff6e02-58a0-8e9d-06b7-37e6934b3df1.gif)

`1`と`2`が2つ返ってきた画面表示と`a`と`b`と`c`の3つが返ってきた画面表示になります。

#さいごに
いかがでしたでしょうか。もしJavaScript側から外部APIを呼び出すようなシステム構造の場合にpuffing-billyを使用してみてはどうでしょうか。