Search code examples
rubyrspecassociationsnested-attributes

How to create factories for models with nested_attributes in Factorybot


I want to test following controller with RSpec

coupons_controller.rb:

class Api::V1::CouponsController < ApiController
  def index
    if params[:profile_id]
      @coupons = Profile.find(params[:profile_id]).coupons
    end
  end
end

I want to know

1) How to create factories with FactoryBot (spec/factories/profiles.rb, coupons.rb, coupon_profiles.rb)

2)How to write spec/controllers/coupons_controllers.rb:

associations

profile.rb

class Profile < ApplicationRecord
  accepts_nested_attributes_for :coupon_profiles
end

coupon.rb

class Coupon < ApplicationRecord
  has_many :coupon_profiles
end

coupon_profile.rb

class CouponProfile < ApplicationRecord
  belongs_to :coupon
  belongs_to :profile
end

Solution

  • Something like:

    # spec/factories/profiles.rb
    FactoryBot.define do
      factory :profile, class: 'Profile', do
        # ...
      end
    end
    # spec/factories/coupons.rb
    FactoryBot.define do
      factory :coupon, class: 'Coupon' do 
        # ...
      end
    end
    # spec/factories/coupon_profiles.rb
    FactoryBot.define do
      factory :coupon_profile, class: 'CouponProfile' do
        coupon
        profile
      end
    end
    

    Honestly, your best bet is reviewing the GETTING_STARTED README for FactoryBot -- everything you want to know is in there, with examples. It is a shining example of a README. (Note on the use of class in my example above, there are specific performance reasons to use the stringified class name instead of the class constant)

    For your controller specs, have you reviewed the RSpec Documentation? Though it's recommended that you use more functional testing like Request specs instead of controller specs. You should be able to do something like:

    describe 'coupons' do 
      subject { response }
    
      shared_examples_for 'success' do
        before { request }
    
        it { should have_http_status(:success) }
      end
    
      describe 'GET /coupons' do
        let(:request) { get coupons_path }
    
        it_behaves_like 'success'
      end
    
      describe 'GET /coupons/:profile_id' do
        let(:request) { get coupon_path(profile)
        let(:profile) { coupon_profile.profile }
        let(:coupon_profile) { create :coupon_profile }
    
        it_behaves_like 'success'
      end
    end