I’m coming back to using fixtures some 10 years since I last touched them, and I think I have grossly misunderstood how they work. Take my Thing model, nothing special, a model with no attributes just a primary key
one: {}
two: {}
I also haven’t written anything without RSpec for 10 years but hey ho…
require "test_helper"
class ThingTest < ActiveSupport::TestCase
fixtures :things
test 'does a thing' do
assert_equal things(:two), Thing.first
assert_equal things(:one), Thing.last
assert_equal 2, Thing.count
end
end
class Thing2Test < ActiveSupport::TestCase
test 'does a thing' do
assert_equal nil, Thing.first
assert_equal nil, Thing.last
assert_equal 0, Thing.count
end
end
It would appear that fixtures are always loaded. or they are loaded and then persist? I thought the fixtures were loaded into memory, then dumped into the database before each test and then rolled back with with each transaction. My real question here is, is there way that I can have fixtures for one test and not another? ATM I am not sure this is currently possible.
The fixtures API does indicate that fixtures are loaded always by default:
The testing environment will automatically load all the fixtures into the database before each test. To ensure consistent data, the environment deletes the fixtures before running the load.
That page has more details on other options that control how the fixtures work. The docs for the fixtures method are nonexistent and it’s not clear to me what that does.
I don’t use fixtures often, but have tried recently (I gave up because I just cannot get them to work reliably and don’t want to use them bad enough to debug it)
Thanks @davetron5000. I’ve just spend the past two days trying to debug how it works. This is what I can fathom.
Fixtures don’t get loaded automatically as such. However… ActiveRecord::Fixtures overrides the default before_setup to call setup_fixtures. setup_fixtures loads the fixtures that you have setup with fixtures :users, :things # or :all. So as long as you’ve defined the fixtures to load, they will be loaded when the first test is run that has fixtures defined. I am looking at this using RSpec so it might be slightly different to Test::Unit.
In this case the first test would run, then the second which loads fixtures kinda lazily before the before block.
RSpec.describe SomeThing do
it 'does something' do
expect(true).to be_true
end
end
RSpec.describe Thing do
fixtures :all
before do
# fixtures are loaded before here
end
it 'does something' do
expect(true).to be_true
end
end
Once loaded those fixtures hang around. The fixtures are loaded and then the transaction for the individual test is started and rolled back at the end of the test leaving the fixture data as it was before the individual test started. The problem I’ve seen with this is that the fixture data bleeds into subsequent tests. So having a situation where you want to have some fixtures in one spec and none in another is something you can’t rely on. This isn’t necessarily what I expected. And the fixture data bleeding into other tests is really annoying. In this case I’ve managed to mitigate it (for now) with forcing the order of our rspec tests to push our fixture tests (specs) to the back of the queue. I need to tweak this to still allow randomised ordering before partitioning.
config.register_ordering :global do |examples|
fixtures, other = examples.partition do |example|
example.metadata[:fixtures].present?
end
other + fixtures
end
Unfortunately I still need to find a solution to loading multiple different fixtures groups and persisting the fixtures most efficiently. I have an idea of what to do vaguely. But it might just be grouping specs and ensuring the database is truncated at the end of each group run which is fiddly but possible.
Unfortunately I still need to find a solution to loading multiple different fixtures groups
Are you you need different fixture groups?
Typically, fixtures will be a predefined set of records that are loaded at the start of your test run and deleted after it. This happens very fast and allows you to write tests/specs that assume those same fixtures are always there. Normally, a test helper file will just load all fixtures so you don’t have to think about it; if you’re running a spec the fixtures are always there.
# /spec/rails_helper.rb
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.global_fixtures = :all
end
When using fixtures, you generally want to avoid doing things like counting the absolute number of records in the database, because you might add another fixture and then your counts would break.
Instead, try to assert (or expect) changes to occur when you do something, for example:
RSpec.describe Thing do
it "can be created" do
thing = Thing.new
expect { thing.save }.to change { Thing.count }.by(1)
end
end
This will pass whether you have 0 Thing fixtures or 1 or 100.
Oh I agree @moveson we need to write tests under the assumption that the fixture data could always there. I needed to roll out something quickly and not get distracted fixing every test that hadn’t previously assumed that.
I mean you’re right I don’t necessarily need different groups in the context of running the test suite, but with different teams working on different sets of specs / features we felt that having a division somewhere is helpful. However, maybe we just need to cope with that since it’s not like in the real world we don’t just have a single set of data in the wild. Yes, that’s decided then
Nevermind, I read the code again. It looks like if you don’t call fixtures then the helper methods (eg. topics(:foo)) aren’t defined within that class. But the full fixture set is always loaded into the database eventually.
I don’t think this is correct, but I will investigate more.
class OverlappingFixturesTest < ActiveRecord::TestCase
fixtures :topics, :developers
fixtures :developers, :accounts
def test_fixture_table_names
assert_equal %w(topics developers accounts), fixture_table_names
assert_not_nil topics(:first)
end
end
class NoFixturesTest < ActiveRecord::TestCase
def test_no_fixtures_defined_in_this_class
assert_equal([], fixture_table_names)
assert_raise NoMethodError do
topics(:first)
end
assert_nil Topic.first
end
end
The test order matters here, but if NoFixturesTest runs second, then assert_nil Topic.first will fail because a Topic will exist (from OverlappingFixturesTest).
So yeah, the fixtures hang around in the database, and the transaction takes care of rolling back any changes / new additions that ensures the tests are fast. But also annoying because to be efficient you should only keep the fixtures for as long as you need them, and grouping by batches of tests that require fixtures and dumping at the end of that batch is the only way to achieve this. I will point out the fixture set I am dealing with is a few hundred Mb so loading it is not a trivial amount of time.
i’m working with a legacy application that relied on rails 4 behavior whereby fixtures did not stick around for subsequent spec files. so i hacked together a solution (using rspec) which seems to maintain this old behavior: only specs which explicitly declare fixtures :all (or whatever fixtures you want) will have those fixtures loaded in the DB for the duration of that file. in (rails|spec)_helper.rbRSpec.configure block:
config.after :all do
conn = ActiveRecord::Base.connection
fixture_table_names.each do |table_name|
conn.delete "DELETE FROM #{conn.quote_table_name(table_name)}", "Fixture Delete"
end
ActiveRecord::FixtureSet.reset_cache
invalidate_already_loaded_fixtures # needed for rails 7.2.2
end
why isn’t this a built-in feature? it’s probably a little slower than the original behavior, but seems super useful. am i missing something?