Search code examples
ruby-on-railsrspecmongoidfactory-botdatabase-cleaner

rails - Objects persisted by FactoryGirl are not available in Controller


I am writing tests for my controllers in admin namespace. Using RSpec (3.5.0), FactoryGirl (4.8.0), DatabaseCleaner (1.5.3) and Mongoid (6.0.3).

The problem is that these test act strangely. When testing GET index request, objects produced by FactoryGirl are successfully created and persisted. However, the controller does not seem to find them.

I have three different controllers. 2 out of 3 have this problem and the third one works like a charm. The code is the same (except for naming) the only difference is that the resource for the working controller is nested.

The one for accessories works:

describe "GET #index", get: true do

    let (:accessory) { FactoryGirl.create(:accessory) }

    before do
      get :index, params: { category_id: accessory.category_id.to_s }, session: valid_session, format: :json
    end

    it "responses with OK status" do
      expect(response).to have_http_status(:success)
    end

    it "responses with a non-empty Array" do
      expect(json_body).to be_kind_of(Array)
      expect(json_body.length).to eq(1)
    end

    it "responses with JSON containing accessory" do
      expect(response.body).to be_json
      expect(json_body.first.with_indifferent_access).to match({
          id: accessory.to_param,
          name: 'Test accessory',
          description: 'This is an accessory',
          car_model: 'xv',
          model_year: '2013',
          images: be_kind_of(Array),
          category_id: accessory.category.to_param,
          dealer_id: accessory.dealer.to_param,
          url: be_kind_of(String)
        })
    end
  end

And the one for categories does not:

describe "GET #index", get: true do

    let (:category) { FactoryGirl.create(:category) }

    before do
      get :index, params: {}, session: valid_session, format: :json
    end

    it "responses with OK status" do
      expect(response).to have_http_status(:success)
    end

    it "responses with a non-empty Array" do
      expect(json_body).to be_kind_of(Array)
      expect(json_body.length).to eq(1)
    end

    it "responses with JSON containing category" do
      expect(response.body).to be_json
      expect(json_body.first.with_indifferent_access).to match({
          id: category.to_param,
          name: 'Test category',
          image: be_kind_of(String),
          url: be_kind_of(String)
        })
    end
  end

As you can see the logic is the same: issuing a request in the before hook and using let to set the object.

Another strange thing is that GET show test for categories with the same logic works perfectly.

In these questions (1, 2) they say it might be due to DatabaseCleaner strategy and one should use truncation instead of transaction strategy. Which I do since Mongoid allows only truncation. And I am also not using JavaScript-enabled tests and specifically told rspec to use_transactional_fixtures = false

RSpec config for FactoryGirl and DatabaseCleaner:

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods

  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

I am able to make these tests pass by issuing request and creating an object in each example instead of using before and let. But I think it should work with them.

Controller index methods are default:

def index
  @thing = Thing.all
end

Do you have any thoughts on this strange behaviour?


Solution

  • Please try let! instead of let.

    Note that let is lazy-evaluated. Category data is generated when you call category.to_param. It does not exist in the before block.

    See also https://relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let