その他
    ホーム 技術発信 DoRuby [rails] FactoryGirlを使用する際の注意点

    [rails] FactoryGirlを使用する際の注意点

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    rspecで思うように動かないケースがあってハマる可能性有り。

    今回の問題を抽象的に表現すると

    FactoryGirlを用いてインスタンスを生成するときにおいて、以下の条件を全て満たす場合には、生成されたインスタンスに意図した値が設定されない場合があります。

    • 条件1:所定のattributeに対して値を設定する方法が「複数」存在する。
    • 条件2:当該attributeに対して、上記「複数の方法のうち少なくとも2つ以上の方法」で値を設定する。

    多分、上記表現では、抽象的すぎて意味不明だと思われるため、以下に具体例を示します。

    事象の具体例

    ケース1

    store = FactoryGirl.create(:store)
    p store.store_category
    => 1
    

    ケース2

    store = FactoryGirl.create(:store, store_category: 2)
    p store.store_category
    => 1
    

    2にならない!!

    参考

    以下の場合には、当然、値を代入できます。

    store = FactoryGirl.create(:store)
    store.store_category = 2
    p store.store_category
    => 2
    

    前提

    • 前提1:Storeテーブルには、store_category(カテゴリ名)というattributeが存在する。
    • 前提2:Storeクラスには、以下のようなメソッドが定義されている。
    class Store < ActiveRecord::Base
               :
    
      # カテゴリ名でカテゴリコードに値を代入
      def store_category_name=(category_name)
        store_category = convert(category_name)
      end
    
      # カテゴリ名をカテゴリコードに変換
      def convert(name)
               :
      end
    
               :
    end
    
    • 前提3:FactoryGirlを用いて、以下のように定義されている。
    FactoryGirl.define do
      factory :store do
        store_category_name 'Japanese Restaurant'
      end
    end
    

    ※「Japanese Restaurant」は、カテゴリコードに変換すると「1」

    「今回の問題を抽象的に表現すると」との対応関係

    上記条件1でいうところの、

    「所定のattribute」は「Storeテーブルのstore_category」であり、

    「所定のattributeに対して値を設定する方法」として、

    • 方法1:store.store_category = 1
    • 方法2:store.store_category_name=’Japanese Restaurant’

    の2つがある。

    どうすれば代入できるか

    ケース3

    store = FactoryGirl.create(:store, store_category_name: 'French Restaurant')
    p store.store_category
    => 2
    

    ※「French Restaurant」は、カテゴリコードに変換すると「2」

    原因

    FactoryGirlのGemの中身を確認した訳ではありませんので、表面的な事象に基づいた話になります。

    恐らくFactoryGirlの内部で、上記各ケースが以下のようになっていると推定されます。

    ・ケース1:「store = FactoryGirl.create(:store)」

    Store.new(store_category_name: 'Japanese Restaurant')
    

    ・ケース2:「store = FactoryGirl.create(:store, store_category: 2)」

    Store.new(store_category: 2, store_category_name: 'Japanese Restaurant')
    

    ・ケース3:「store = FactoryGirl.create(:store, store_category_name: ‘French Restaurant’)」

    Store.new(store_category_name: 'French Restaurant')
    

    そして、Storeのinitializeの中で、渡されたHashの順番にattributeを更新していき、

    ケース2では、

    代入1回目:最初にstore_categoryに2が入る
    代入2回目:その後にstore_category_nameにJapanese Restaurantが渡されて、store_categoryに1が入る
    

    となっているのだと思います。

    「今回の問題を抽象的に表現すると」との対応関係

    このように、ケース2では、代入1回目と代入2回目で異なる方法(「複数の方法のうち少なくとも2つ以上の方法」)で、値を設定していることから条件2を満たすことになり、意図した値が設定されていません。

    ケース1, 3では、1つの方法でのみ値を設定しているため、条件2を満たすことなく、意図通りの値が設定されます。

    対策

    今のところ、各ソースコードの確認や実際にテストする際に値が意図したものに設定されているか等を確認しながら気を付けるくらいしか思い付きません。

    こういう問題があるということを頭の片隅に入れておけば、問題が生じたときのデバッグが早くなると思います。

    参考

    selectable_attrというgemを使うと、所定のattributeに値を設定するために複数のメソッドが追加されます。

    selectable_attrの対象としたattributeに対してFactoryGirlを使って値を初期化する場合には気をつけましょう。

    最後に

    rspecを記載したときには、各オブジェクトの値が意図したものになっているかを、しっかりと確認しましょう。

    そうしないと意味の無いテストを行ってしまっている可能性があります。