Why would rails not reset the test database between runs
Asked Answered
C

4

7

There are comments in the rails codebase that indicate that the test database should be reset between runs

rake -T

rake test:all                           # Run tests quickly by merging all types and not resetting db
rake test:all:db                        # Run tests quickly, but also reset db

config/database.yml

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:

This doesn''t seem to be the case for me.

I'm using factory girl generate test models, here is an example factory

FactoryGirl.define do
  factory :podcast do
    sequence(:title)     { |n| "Podcast #{n}" }
    sequence(:feed_url)  { |n| "http://podcast.com/#{n}" }
  end
end

The podcast should have a unique feed_url so I validate it's uniqueness in the model.

class Podcast < ActiveRecord::Base
  validates :feed_url, uniqueness: true, presence: true
end

In test_helper.rb I lint all factories

ENV["RAILS_ENV"] ||= "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'minitest/autorun'

FactoryGirl.lint

My test creates a podcast, builds another with the same name, then asserts that the second is invalid.

require 'test_helper'

describe Podcast do
  describe '#feed_url' do
    it 'must be unique' do
      podcast = create(:podcast)
      new_podcast = build(:podcast, feed_url: podcast.name)

      assert_invalid podcast, :feed_url, 'has already been taken'
    end
  end
end

The first time I run the tests it executes without errors and the tests all pass. The second time I run the tests the Factory Girl lint fails because podcast feed_url has already been taken.

Why isn't the test database being rebuilt between runs?

Cannell answered 12/6, 2014 at 23:54 Comment(0)
V
8

We have a more involved FactoryGirl set up that prepares our database with some canonical items, but I think you could probably put this code directly in your test_helper.rb to assure the database is emptied:

# Destroy all models because they do not get destroyed automatically
(ActiveRecord::Base.connection.tables - %w{schema_migrations}).each do |table_name|
  ActiveRecord::Base.connection.execute "TRUNCATE TABLE #{table_name};"
end

Alternatively, run rake db:test:prepare before every run.

There is a gem too that you can use, but I don't have any experience with it: http://rubygems.org/gems/database_cleaner.

Versicular answered 13/6, 2014 at 0:4 Comment(0)
J
8

The reason the database is not resetting is that you are running your tests outside of the database transaction that rails provides. The ActiveSupport::TestCase class is the basis for all rails tests. ActiveRecord adds a per-test database transaction to this class. This transaction will reset the database after each test. But you aren't running your tests with ActiveSupport::TestCase, you are running your tests with Minitest::Spec which isn't configured to run the transaction.

The simplest solution is to add minitest-rails to your Gemfile, and change the require in your test_helper.rb file from minitest/autorun to minitest/rails. If you would rather add your own support for Minitest's spec DSL you can use this article as a starting point.

Jacobah answered 13/6, 2014 at 15:55 Comment(1)
Thanks for answering the question, is there an advantage to using transactional rollbacks between tests over truncating the database between runs as rdnewman suggested?Cannell
N
2

Do you have another factory that might be creating a podcast via an association?

FactoryGirl linting builds each factory and checks it's validity, and if another factory has a podcast as an association, it'll create a podcast record.

FactoryGirl recommends clearing the database after running the linting. They use database_cleaner in their example: https://github.com/thoughtbot/factory_girl/blob/2bf15e45305ac03315cf2ac153db523d3ce89ce1/GETTING_STARTED.md#linting-factories

Ninfaningal answered 13/6, 2014 at 0:3 Comment(2)
I accepted rdnewman's answer because it doesn't introduce more dependencies. Can you make a case for using database_cleaner?Cannell
If rdnewman's answer is working for you, then I'd stick with that, it's simple enough. database_cleaner achieves the same goal, it just happens to support more ORMs and more strategies for the DB cleaning.Ninfaningal
C
1

If you're using 'Rspec' to be your unit test framework. After the installation of gem 'rspec-rails', you will got one configuration file called: spec/rails_helper.rb and within it you will find one configuration which looks like this:

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

this means that if it is true, then your each teat case will be running in a separate transaction.

Crenelate answered 22/9, 2022 at 7:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.