こんにちは。SI部の r_maeda です。
みなさん、FactoryBot gem はご存知でしょうか?
https://github.com/thoughtbot/factory_bot
FactoryBot は、Ruby オブジェクトを生成するための factory を、簡単な DSL で定義できる gem です。
RSpec gem と共に、Ruby (on Rails) で書いたアプリケーションのテストコードを書くために広く利用されている gem の1つではないでしょうか。
この FactoryBot gem ですが、生成できるオブジェクトは ActiveRecord モデルのインスタンスだけではありません。任意のクラスのインスタンスを生成することが可能です。
そんな FactoryBot gem の面白い使い方を発見したので、ご紹介したいと思います。
FactoryBot を使って、JSON 文字列を生成してみます。まずは json factory を以下の通り定義します。
# frozen_string_literal: true
require "factory_bot"
require "json"
FactoryBot.define do
factory :json, class: "String" do
hoge { :foo }
fuga { :bar }
initialize_with do
JSON.generate(attributes)
end
end
end
この factory を使って実際に JSON 文字列を生成してみます。
% irb
irb(main):001:0> require "./json_factory"
=> true
irb(main):002:0> FactoryBot.build(:json)
=> "{\"hoge\":\"foo\",\"fuga\":\"bar\"}"
irb(main):003:0>
できました。
今回作成した json factory のポイントは、initialize_with
ブロックです。
このブロックを定義することで、「どのような手順で、生成したインスタンスに値を設定するか」を上書きすることが出来ます。
initialize_with
ブロックを定義しない場合、FactoryBot は以下のようなコードを実行しようとします。
s = String.new
s.hoge = :foo
s.fuga = :bar
String
class に #hoge=
メソッドは存在しないため、当然このコードはエラーになります。
しかし、initialize_with
ブロックを定義してあげることで、FactoryBot に「どうやってインスタンスを生成すればよいか、またどうやって生成したインスタンスに値を設定すればよいか」を指示することが出来ます。
私が普段関わっているシステムには、「他のシステムに HTTP リクエストを行う」処理が多く存在しています。
これらの処理のテストを書くために WebMock gem を導入しているのですが、この gem を使って「特定のリクエストに対するダミーレスポンスを設定する」場面などで使えるんじゃないかなと考えています。
# JSON 文字列を生成
response_body = FactoryBot.build(:get_api_v1_users_response)
# HTTP リクエストに対する stub の設定
WebMock
.stub_request(:get, "https://example.com/api/v1/users")
.to_return(status: 200, headers: { "Content-Type": "application/json" }, body: response_body)
今回は FactoryBot gem を使って JSON 文字列を生成する方法を紹介しました。
他にも Hash
のインスタンス、Struct
や Data
のサブクラスのインスタンスなども、同様に initialize_with
ブロックを定義してあげることで生成できるようになります。
なお association
を使うこともできるので、「ネストが存在するなど、それなりに複雑な JSON を生成したい」場合には、適宜 factory を分割していくのがよいと思います。
# frozen_string_literal: true
require "factory_bot"
require "json"
FactoryBot.define do
factory :json, class: "String" do
hoge { :foo }
fuga { :bar }
association :child
initialize_with do
JSON.generate(attributes)
end
end
factory :child, class: "Hash" do
piyo { :baz }
initialize_with do
attributes
end
end
end
この記事が、ここまで読んで頂いた皆様のお役に立ちますと幸いです。
WebMock gem には WebMock::RequestStub#to_return_json
といったメソッドが存在するそうです。これを使えば、簡単に JSON WebAPI のダミーレスポンスを設定できるみたいですね。
https://github.com/bblimke/webmock#response-with-json-body
body = { hoge: :foo, fuga: :bar }
# 中で body.to_json してくれる
# Content-Type: "application/json" リクエストヘッダも勝手に追加してくれる
WebMock
.stub_request(:get, "https://example.com")
.to_return_json(status: 200, body: body)
「FactoryBot で JSON 文字列を生成したところで何に使えるの?」についてはその用途が1つ減ってしまいましたが、「#initialize_with
を使えば様々な class のインスタンスが生成できる」といった話は頭の片隅に置いておいてもらえれば、役に立つこともあるのかなと思います。
「#to_return_json
に渡す body (Hash) を FactoryBot を使って生成する」なんてこともできますしね。