开发者

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 .

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜