こんにちは。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 = :barString 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 を使って生成する」なんてこともできますしね。