Search code examples
ruby-on-railspostgresqlrspec-railsdatabase-cleaner

Loading a Database of Fixtures in Rails


I am building a Rails App (I am new to this, so forgive me if some of the wording is clumsy). I am trying to write tests (with RSpec) which draw and use data from the database, and I am having trouble writing the tests in a concise way.

Some of the tests (such as signing up a user, or creating content) seem to be best suited to having a fresh database, while some require a database populated fixtures.

At the moment I am using the database cleaner gem, with the following configuration:

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

  # start the transaction strategy as examples are run    
   config.around(:each) do |example|
     DatabaseCleaner.cleaning do
       example.run
     end    
   end

The reason I am using truncation for both strategies is that I prefer to have the id values completely refreshed between examples (so that if I create in one test, and then create in a second test, the second example should have id 1 rather than 2). I am not sure what the various strategies mean exactly - I have found this question which appears to explain them in terms of SQL syntax, but I'm not very familiar with that so my understanding is still quite vague. I believe the database is managed with PostgreSQL, but I rarely have to interact with it directly through that so I'm not particularly experienced.

So my database is completely dropped and constructed from scratch between every example - if I want a clean database then this is ideal, but if I want to simply load the fixtures then it can take a while to create all of the models. It feels like I should be able to have a 'cached' version of the fixtures, which I can load for those examples it's appropriate for. But I have no idea how to do this, if it is even possible. Is there a way?

Edit: Following a discussion in the comments, I suspect that I may want to remove Database Cleaner and use default Rails fixtures instead. I have tried this, and the only problem I'm having with it is the same as I had with the transaction strategy described above. That is: when test-created records are rolled back, the id is not rolled back, and this is awkward behaviour. If I create a user for the purpose of running a test, it is convenient to refer to it just as User.find(1), which is impossible if the id does not reset.

It might be that this is some kind of red flag, and I shouldn't be doing it (I am open to doing something else). I realise too that I could just say User.first to get the same behaviour, and this might be better. I'm not sure what's appropriate.


Solution

  • DatabaseCleaner is not intended to be used with fixtures. Its intended to be used with factories. ActiveRecord::Fixtures has its own rollback mechanism.

    There is a really big conceptional difference.

    Fixtures are like this huge set of static dummy data that gets thrown into the database for each example and then gets reset with a transaction. The big con of fixtures is that the more fixtures you have the more complex the initial state of the application is and it encourages a tight coupling between the tests and the fixtures themselves.

    This is an example which shows how the value "Marko Anastasov" magically appears from somewhere outside the code:

    RSpec.describe User do
      fixtures :all
    
      describe "#full_name" do
        it "is composed of first and last name" do
          user = users(:marko)
          expect(user.full_name).to eql "Marko Anastasov"
        end
      end
    end
    

    Although fixtures have hade a resurgence lately due to perceived simplicity (along with Minitest).

    Factories are object factories that produce unique records. Instead of having a bunch of junk floating around you start each example with a blank state and then use the factories to populate the database with the exact state to replicate the scenario you are testing. Done right this minimizes test ordering issues, flapping tests and changing fixtures breaking tests.

    RSpec.describe User do
      describe "#full_name" do
        it "is composed of first and last name" do
          user = FactoryBot.create(:user)
          expect(user.full_name).to eql "#{user.first_name} #{user.last_name}"
        end
      end
    end
    

    This is an example of a good factory that would generate psuedorandom values:

    require 'ffaker'
    
    FactoryBot.define do
      factory :user do
        first_name { FFaker::Name.first_name }
        last_name { FFaker::Name.last_name }
      end
    end