Search code examples
ruby-on-railsrspecfactory-bot

FactoryBot presenting unexpected behaviour when testing an instance variable assignment in a controller action with rSpec in a Rails 7 application


I'm writing controller tests with Rspec in a Rails 7 application and getting some unexpected behaviour when asserting against an assignment.

The controller action that I'm testing returns the instance variable @prefectures;

  def edit
    @prefectures = Prefecture.all.order(:code)
    @city = City.find(params[:id])

    respond_to do |format|
      format.html { render :edit, locals: { city: @city } }
    end
  end

In the tests I'm using FactoryBot to create a list of three Prefectures and then checking in the example that the assigned @prefectures matches those three.

RSpec.describe 'Cities', type: :request do
  let(:city) { create(:city) }
  let(:prefecture_list) { create_list(:prefecture, 3) }

  describe 'GET /edit' do
    it 'succeeds' do
      get edit_city_path(city)
      expect(response).to be_successful
      expect(response.status).to eq(200)
    end

    it 'assigns @prefectures' do
      expected = prefecture_list.sort_by { |prefecture| prefecture.code }
      get edit_city_path(city)
      expect(assigns(:prefectures)).to eq(expected)
    end
  end
end

There are several actions on the controller that return an instance variable of @prefectures. There are also a number of tests in the test file that assert the assignment of @prefectures using the same FactoryBot list. In each instance FactoryBot instantiates a collection of three prefectures and the test confirms that the assigned '@prefectures' is that same collection of three Prefectures.

In the 'GET /edit' tests, however, the test fails because for reasons I don't understand, the assigned @prefectures is a collection of 6 Prefectures rather than 3. Each time it runs it's a distinct collection of six so it's not as if three are somehow being saved between test runs and added to. It seems as though in this test instance FactoryBot is creating a list of 6 rather than three.

Any ideas on why this is happening?


Solution

  • I think I figured it out. In the test setup I also create a city_list;

    let(:city_list) { create_list(:city, 3) }
    let(:prefecture_list) { create_list(:prefecture, 3) }
    

    The Prefecture factory creates a Prefecture;

    FactoryBot.define do
      factory :prefecture do
        name { Faker::Address.state }
        code { Faker::Number.number(digits: 3) }
      end
    end
    

    The City factory creates a City with a Prefecture because there is a one-to-many relationship between the two;

    FactoryBot.define do
      factory :city do
        prefecture { create(:prefecture) }
        name { Faker::Address.city }
        rating { Faker::Number.within(range: 0.0..5.0) }
      end
    end
    

    The test that is failing is unique among the tests that assert the @prefectures assignment as it is the only one that also uses city_list. city_list appears to create an additional set of three 'Prefectures`.

    I fixed the issue by overriding the prefecture_id constraint of the city_list to use one of the existing prefectures rather that generate it's own new one and the test now passes.

    let(:prefecture_list) { create_list(:prefecture, 3) }
    let(:city_list) { create_list(:city, 3, prefecture_id:prefecture_list[0].id) }