Shinosaka.rb Advent Calendar 1日目の記事です。
#はじめに
戯言です。本題を見たい人は次へ読み進めることをオススメします。
昨今フロントエンドのフレームワークないし、ライブラリの発展によりフロントエンドとバックエンド別々にテストを実行することが珍しくないと思います。ですが、最終的にはE2Eテストも"したい"、"やらないといけない"と考えた場合に厄介な問題が出てきます。
今回はその問題の1つである外部サイト(外部API)呼び出しのモックについて書きたいと思います。
#前提条件
##システム構成
まずはシステム構成で以下のような方を対象とした記事です。
SPAで構成する場合はこんなことが往々に発生するのではないでしょうか。(サーバはRailsでなくても可)
この構成時のE2Eテストを実施する場合に、実際に外部APIを叩いてしまっては困ります。サーバ側から外部APIに投げる場合がある場合はwebmockを使用すればよいのですが、今回はそれでは不可能です。そこでpuffing-billyというAPI呼出をモック可能なJavaScriptDriverを使用します。
##主な使用フレームワーク(ライブラリ)
Rails:5.0.0.1
capybara:2.10.1
puffing-billy:0.9.1
Reactjs:15.4.1
#サンプルプログラム
今回はシステム構成で紹介したプログラムとなるようなサンプルソースをこちらに用意しました。以下に軽く解説をしておきます。
##テスト対象の確認
まずはテストするコードを一部抜粋します。
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
というアクションを渡しています。
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
はボタンをクリックすると動作するようにしています。
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
というファンクションを実行しています。
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で取得しています。
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
します。
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
に以下の設定を足します。
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が必要なのでそれはサイトにいって落としてきて下さい
##テストコード
では最後にテストコードを見てみます。
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
を動かせば正常にテストが完了します。
実際に動かした場合は以下のように動作するはずです。
1
と2
が2つ返ってきた画面表示とa
とb
とc
の3つが返ってきた画面表示になります。
#さいごに
いかがでしたでしょうか。もしJavaScript側から外部APIを呼び出すようなシステム構造の場合にpuffing-billyを使用してみてはどうでしょうか。