Rspec and Shoulda - Complementary or Alternatives

rspec and shoulda - complementary or alternatives?

I have to argue against Chris's answer that they are alternatives. I use Shoulda and Rspec together in my Rails application, and they complement each other well.

This combo allows me to write concise one-line unit tests for recurring things like associations and validations, as well as the having the full rspec suite for more complex specs. You get the best of both worlds without any conflicts.

Check out the Shoulda README which shows how to install along side Rspec. It even says it provides "Test::Unit- and RSpec-compatible one-liners that test common Rails functionality. These tests would otherwise be much longer, more complex, and error-prone."

Edit (examples):

At the top of my specs, I always declare my Class relationship and validation tests which are concise and easy to read.

describe Component do

context 'relationships' do
it { should belong_to(:technology)}
it { should have_many(:system_components) }
it { should have_and_belong_to_many(:variables) }
it { should have_many(:images).dependent(:destroy) }
it { should have_many(:documents).dependent(:destroy) }
end

context 'validations' do
it { should validate_presence_of(:make) }
it { should validate_presence_of(:model) }
it { should ensure_length_of(:name).is_at_most(100) }
it { should validate_presence_of(:technology_id) }
end
end

Then the rest of my spec will have more complex tests where I am using mocks and stubs which come from Rspec.

RSpec vs. Shoulda?

RSpec has a lot of nice features, more than just the syntax too. I would recommend it over Shoulda.

Even Thoughtbot, the authors of Shoulda, are using RSpec now. Shoulda now works as an RSpec plugin and it provides a number of useful matchers.

Alternatives to Shoulda?

The remarkable gem is the closest I've found to Shoulda. Unfortunately it looks like it hasn't been updated in even longer. It also relies on RSpec and it not compatible with Test::Unit.

It isn't clear to me how well it supports Rails 3 - although there are some instructions for getting it up and running.

After looking into both Shoulda and Remarkable I decided to go with Shoulda as it still seemed the more likely set of helpers to be updated.

One of the issues you will have with shoulda is that Thoughtbot uses RSpec rather than Test::Unit, which is part of the reason that the Shoulda gem (which exists for the benefit of Test::Unit users) has seen little activity - the RSpec component of the gem, shoulda-matchers, has been updated much more recently. Thoughtbot also found a new maintainer for shoulda-contextin Jan 2012 (this is the gem required alongside shoulda-matchers to get everything working in Test::Unit - the shoulda gem really only pulls in shoulda-matchers and shoulda-context) so there should be more focus on the Test::Unit side moving forward I expect.

Hopefully these moves give at least a little confidence that Shoulda for Test::Unit will be updated more frequently moving forward. In the absence of any other suggestions I would stick with Shoulda for the time being.

Why should I use RSpec or shoulda with Rails?

RSpec and similar frameworks are tooling designed to aid in Behavior Driven Development. They're not just a prettier way to write tests, though they do help with that.

There is plenty of information on BDD here: http://behaviour-driven.org/
And wikipedia: http://en.wikipedia.org/wiki/Behavior_Driven_Development

There are too many benefits to list here, so I'd recommend browsing that site a little.

BDD on Rails - Is the community more behind Shoulda or RSpec?

The rails community is in favor of both RSpec and Shoulda. It depends of the developer.
If you prefer Shoulda, use it.

If you prefer RSpec, use it ;)

They're both different library with a similar goal. It doesn't mean every developer has to be for or against it. It only means that you can use either of them.

It's up to you to make your choice depending of your preferences (and the other developers you're working with).

Using shoulda to refactor rspec tests on Rails models

1) The Shoulda::Matchers::ActiveRecord module has a lot more in it than just column and index matchers. I would dig around in the included classes a little and see what you can find. This is where the have_many, belong_to etc come from. For the record though, I see little value in most of what is in there.

2) Yes, macros such as have_many test a lot more than whether or not a model responds to a method. From the source code, you can see exactly what it is testing:

def matches?(subject)
@subject = subject
association_exists? &&
macro_correct? &&
foreign_key_exists? &&
through_association_valid? &&
dependent_correct? &&
class_name_correct? &&
order_correct? &&
conditions_correct? &&
join_table_exists? &&
validate_correct?
end

3) Making the tests more readable and/or concise is definitely a subjective question to answer. Everyone will give you a different answer to this depending on their background and experience. I would personally get rid of all of the respond_to tests and replace them with tests that have value. When someone looks at your tests, they should be able to understand the public API for that class. When I see that your objects respond_to something like "following?", I can make assumptions, but don't really know what it means. Does it take an argument? Does it return a boolean value? Is the object following something or is something following the object?

RSpec / Shoulda validate_presence_of multiple attributes syntax

As far as I know, there is nothing built-in to Shoulda for this. Often you will want to chain options to the shoulda macros, e.g. .with_message(...), so your syntax suggestion wouldn't be possible for those cases.

You could instead do something like:

[:text, :user, :commentable].each do |field|
it { should validate_presence_of(field) }
end

However, I wouldn't worry too much about having a little bit of duplication in your test suite for the sake of easier reading and maintaining.

Shoulda/RSpec: Make sure that validation message xxx is on :base

Right, it's looking good. Inline rspec tests are using subject. You could rewrite your test like this:

describe 'my method' do
before { contact.valid? }

context 'contact is not valid' do
subject { contact.errors[:base] }
it { should include 'xxx' }
end
end

The should method is called on the subject. It can be more readable sometimes. And you don't have to write descriptions for specs that are self-explanatory ;-)

why rspec test requires save and reload all time

This sounds just fine. Normally your controller actions do database fetches to grab their data. In your test you might massage the data, but if you don't save it first, then your controller won't get the data you expect. Imagine this little scenario:

# in your test ...
user = User.new
get :index
assigns(:users).should eq [user]

# in your controller ...
def index
@users = User.all
end

This test will fail because the user in the test is never saved, so when the controller does User.all, it can't find the user.

Similarly with your test, when you update the attributes and your controller fetches the record, it doesn't know about your changes because they haven't been saved to the database. You definitely need to save your @creative model.

As for the reload, when you fetch a model from the database or make one brand new, it's attributes are stored in an in-memory instance. When you changed the first_visit attribute to false, you changed it in memory. When you saved the instance, it was applied to the database. When you called your index action, it updated the Creative instance it fetched to have first_visit be true. However, in your test you're still using the in-memory object which has it as false. The reload forces the object to be refetched from the database, which allows you to see it's current and updated attributes.

An alternative to reloading would be, if your controller set an instance variable, like @creative, to access it via the assigns() helper, like so:

get :index
assigns(:creative).first_visit.should eql(true)

That's saying that the instance variable @creative (that the controller set) will have a first_visit attribute set to true.



Related Topics



Leave a reply



Submit