RSpec + DatabaseCleaner help -- teardown happening prematurely
I'm a little lost with RSpec having always stuck to xUnit-based testing frameworks, but I'm giving it a go.
The nested nature of the way specs are written is giving me some headaches with regards to where I'm supposed to do database setup/teardown though.
According the to DatabaseCleaner README:
Spec::Runner.configure do |config|
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
Now I can't use transactions, because I use them in my code, so I'm just sticking to truncation, but that should be neither here nor there.
I have this:
RSpec.c开发者_Go百科onfigure do |config|
config.mock_with :rspec
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
The problem here is that any fixtures I create in a subject
or let
block have already disappeared (from the database) when I try to use them in a following describe
or it
block.
For example (using Machinist to create the fixtures... but that shouldn't be relevant):
describe User do
describe "finding with login credentials" do
let(:user) { User.make(:username => "test", :password => "password") }
subject { User.get_by_login_credentials!("test", "password") }
it { should == user }
end
end
I'm struggling with how I'm supposed to be nesting these describe
and subject
and other blocks, so maybe that's my problem, but basically this fails because when it tries to get the user from the database, it's already been removed due to the after(:each)
hook being invoked, presumably after the let
?
If you're going to use subject
and let
together, you need to understand how/when they are invoked. In this case, subject
is invoked before the user
method generated by let
. The problem is not that the object is removed from the db before subject
is invoked, but that it is not even created at that point.
Your example would work if you use the let!
method, which adds a before
hook that implicitly invokes the user
method before the example (and therefore before subject
is invoked).
That said, I'd recommend you stop struggling and use simpler API's that RSpec already exposes:
describe User do
it "finds a user with login credentials" do
user = User.make(:username => "test", :password => "password")
User.get_by_login_credentials!("test", "password").should eq(user)
end
end
That seems much simpler to me.
You wrote:
The problem here is that any fixtures I create in a subject or let block have already disappeared (from the database) when I try to use them in a following describe or it block.
That's right, that's how it works. (And you're not using fixtures in the usual Rails sense, but factories -- just as well, since Rails fixtures suck.)
Every individual spec (that is, every it
block) starts (or should start) from a pristine database. Otherwise your tests would leak state and lose atomicity. So you should create every record you need within the spec in which you need it (or, as David said, in a before
block to cut down on repetition).
As for organizing your specs...do it any way that makes sense. Usually there will be an outer describe
block for the whole class, with inner describe
blocks for groups of related behavior or specs that need a common setup. Each describe
context can have its own before
and after
blocks. These get nested as you would expect, so the order of execution is something like
outer before
inner before
spec
inner after
outer after
If you'd like to see a project with a large number of RSpec specs and Cucumber stories (though for slightly old versions of each), check out http://github.com/marnen/quorum2 .
精彩评论