In one of my rspec test files, I need to run the following (otherwise identical) test in several different contexts:
describe "view the event with request" do
before {
click_link("Swimming lessons")
}
it { should have_content "Party Supplies"}
describe "view the contribute form" do
before {
click_button("Party Supplies")
}
it {
within("#bodyPopover") {
should have_content('cupcakes')
should have_content('napkins')
}
}
end
end
I'd like to be able to put all this in a method (say view_the_event_and_contribute_form) and just use that method in several places in the rest of the test. Can that be accomplished? I tried defining a method which simply had that code, but it didn't recognize describe from within that method.
What's the best way to do this?
You can turn those tests into a shared_example:
shared_example "event with request" do
before { click_link("Swimming lessons") }
it { should have_content "Party Supplies"}
describe "view the contribute form" do
before { click_button("Party Supplies") }
specify {
within("#bodyPopover") {
should have_content('cupcakes')
should have_content('napkins')
}
}
end
end
From other tests use it_behaves_like to run the shared examples:
describe 'Some other awesome tests' do
it_behaves_like "event with request"
end
You almost had it. Just remove the describe, before and it blocks and move that code within a plain method.
In the below spec, I want to test that for a Notification instance, running the destructive method #mark_as_dismissed will change the has_been_read column to true. How do you do that while keeping the test nice and terse?
context "#mark_as_dismissed" do
subject { create(:notification) }
subject.mark_as_dismissed # How do I specify this
its(:has_been_read) { should be_true }
end
There's multiple ways to write tests and different preferred syntaxes, so this is quite an opinionated answer.
Following your style, it would look more like this:
describe "#mark_as_dismissed" do
subject { create(:notification) }
before { subject.mark_as_dissmissed }
its(:has_been_read) { should be_true }
end
Mine would be more like this:
describe "#mark_as_dismissed" do
let(:notification) { create(:notification) }
it "marks the notification as read" do
notification.mark_as_dissmissed
notification.has_been_read.should be_true
end
end
syntatic sugar: Rspec allows a special syntax for methods returning booleans. I would have to test it, I am not sure it would work in this case but perhaps you can do something like:
# for the first alternative
it { should have_been_read }
# for the second alternative
it "marks the notification as read" do
notification.mark_as_dissmissed
notification.should have_been_read
end
Bonus points
To remove the db dependency you can just 'build' the notification instead of using 'create' (which persists the model in the database). If the #mark_as_dismissed method does not need the db (can do a non persistent update), then the test should still work.
build(:notification) # instead of create(:notification)
I recently started learning RoR and TDD, and am having trouble figuring out the best way to handle this scenario.
I have an ActiveRecord model with two fields which share the same validations.
How do I write an RSpec test which utilizes the same tests for the similar fields?
"shared examples" looked like a promising feature to utilize in this scenario, but does not seem to work, as I need to test the entire model but am only passing the individual field to the shared example.
Below is my failed attempt:
describe Trip do
before do
#trip = trip.new(date: '2013-07-01', city_1: "PORTLAND",
city_2: "BOSTON")
end
subject { #trip }
shared_examples "a city" do
describe "when not uppercase" do
before { city = city.downcase }
it { should_not be_valid }
end
end
describe "city_1 must be valid" do
it_should_behave_like "a city" do
let!(:city) { #trip.city_1}
end
end
describe "city_2 must be valid" do
it_behaves_like "a city" do
let!(:city) { #trip.city_2}
end
end
end
This fails because updating the city variable does not update trip model. Is there a way to dynamically tie it back to the model?
BTW, all the tests work on their own if I paste under each field. It just will not work in the context of the shared_example.
Any guidance would be greatly appreciated.
You can perform the assertion within a loop, and use a little metaprogramming, but I would advise against it. Tests should be as simple and straightforward as possible, even if that means having a little duplication. If only two fields are involved, just repeat it.
After learning about shoulda-matchers by answering another StackOverflow question on attribute accessibility tests (and thinking they were pretty awesome), I decided to try refactoring the model tests I did in The Rails Tutorial in an attempt to make them even more concise and thorough. I did this thanks to some inspiration from the documentation for modules Shoulda::Matchers::ActiveRecord and Shoulda::Matchers::ActiveModel, as well as this StackOverflow answer on structuring shoulda tests in models. However, there's still a few things I am not sure about, and I am wondering how these tests could be made better.
I will use the User spec in the Rails Tutorial as my example as it is the most detailed, and covers lots of areas that could be improved. The following code example has been changed from the original user_spec.rb, and replaces the code down until the describe "micropost associations" line. The spec tests against the user.rb model, and its factory is defined in factories.rb.
spec/models/user_spec.rb
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# remember_token :string(255)
# admin :boolean default(FALSE)
#
# Indexes
#
# index_users_on_email (email) UNIQUE
# index_users_on_remember_token (remember_token)
#
require 'spec_helper'
describe User do
let(:user) { FactoryGirl.create(:user) }
subject { user }
describe "database schema" do
it { should have_db_column(:id).of_type(:integer)
.with_options(null: false) }
it { should have_db_column(:name).of_type(:string) }
it { should have_db_column(:email).of_type(:string) }
it { should have_db_column(:created_at).of_type(:datetime)
.with_options(null: false) }
it { should have_db_column(:updated_at).of_type(:datetime)
.with_options(null: false) }
it { should have_db_column(:password_digest).of_type(:string) }
it { should have_db_column(:remember_token).of_type(:string) }
it { should have_db_column(:admin).of_type(:boolean)
.with_options(default: false) }
it { should have_db_index(:email).unique(true) }
it { should have_db_index(:remember_token) }
end
describe "associations" do
it { should have_many(:microposts).dependent(:destroy) }
it { should have_many(:relationships).dependent(:destroy) }
it { should have_many(:followed_users).through(:relationships) }
it { should have_many(:reverse_relationships).class_name("Relationship")
.dependent(:destroy) }
it { should have_many(:followers).through(:reverse_relationships) }
end
describe "model attributes" do
it { should respond_to(:name) }
it { should respond_to(:email) }
it { should respond_to(:password_digest) }
it { should respond_to(:remember_token) }
it { should respond_to(:admin) }
it { should respond_to(:microposts) }
it { should respond_to(:relationships) }
it { should respond_to(:followed_users) }
it { should respond_to(:reverse_relationships) }
it { should respond_to(:followers) }
end
describe "virtual attributes and methods from has_secure_password" do
it { should respond_to(:password) }
it { should respond_to(:password_confirmation) }
it { should respond_to(:authenticate) }
end
describe "accessible attributes" do
it { should_not allow_mass_assignment_of(:password_digest) }
it { should_not allow_mass_assignment_of(:remember_token) }
it { should_not allow_mass_assignment_of(:admin) }
end
describe "instance methods" do
it { should respond_to(:feed) }
it { should respond_to(:following?) }
it { should respond_to(:follow!) }
it { should respond_to(:unfollow!) }
end
describe "initial state" do
it { should be_valid }
it { should_not be_admin }
its(:remember_token) { should_not be_blank }
its(:email) { should_not =~ /\p{Upper}/ }
end
describe "validations" do
context "for name" do
it { should validate_presence_of(:name) }
it { should_not allow_value(" ").for(:name) }
it { should ensure_length_of(:name).is_at_most(50) }
end
context "for email" do
it { should validate_presence_of(:email) }
it { should_not allow_value(" ").for(:email) }
it { should validate_uniqueness_of(:email).case_insensitive }
context "when email format is invalid" do
addresses = %w[user#foo,com user_at_foo.org example.user#foo.]
addresses.each do |invalid_address|
it { should_not allow_value(invalid_address).for(:email) }
end
end
context "when email format is valid" do
addresses = %w[user#foo.COM A_US-ER#f.b.org frst.lst#foo.jp a+b#baz.cn]
addresses.each do |valid_address|
it { should allow_value(valid_address).for(:email) }
end
end
end
context "for password" do
it { should ensure_length_of(:password).is_at_least(6) }
it { should_not allow_value(" ").for(:password) }
context "when password doesn't match confirmation" do
it { should_not allow_value("mismatch").for(:password) }
end
end
context "for password_confirmation" do
it { should validate_presence_of(:password_confirmation) }
end
end
# ...
end
Some specific questions about these tests:
Is it worth testing the database schema at all? A comment in the StackOverflow answer mentioned above says "I only test things that are related to behavior and I don't consider the presence of a column or an index behavior. Database columns don't just disappear unless someone intentionally removes them, but you can protect against that with code reviews and trust", which I agree with, but is there any valid reason why the structure of the database schema would be tested for, and thus justifying the existence of the Shoulda::Matchers::ActiveRecord module? Perhaps just the important indexes are worth testing...?
Do the should have_many tests under "associations" replace their corresponding should respond_to tests under "model attributes"? I can't tell whether the should have_many test just looks for the relevant has_many declaration in a model file or actually performs the same function as should respond_to.
Do you have any other comments/suggestions to make these tests more concise/readable/thorough, both in content and structure?
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?
Your question touched on a few points, I would like to address two of them:
The answer is subjective, so I will give you my personal take.
1) Test ActiveRecord that way?
My answer is yes. You could write complex tests with real data but if you basically trust ActiveRecord you can do it this way and if you get to doing tdd, with these tests first they can help in that process.
2) Write the model tests at all?
My answer is yes. What I do is focus controller and request specs on the happy path and then for cases where validations and the like are needed I write unit model tests for them. This has turned out to be a good division of responsibility to me.
I think this whole thing should be seen from specification point of view.
If you have a component-testing-level specification which covers the necessary database columns for the given model, you should, otherwise not.
If not covered, but as a responsible developer you feel it important to have (your sw and its quality characteristics are better that way), you have to arrange to include this info in the spec, then you can put these tests in the test suite.
Lower testing levels' requirements mostly come from within your organization (internal docs), customer mostly provides only the customer requirement spec (let's say this is the highest level in testing V-model).
As your organization starts design the sw creates the lower test levels specs step by step.
For the "do we really need this" question: it depends on many things: the app complexity, safety critical or not, standards to follow, contractual/legal/industrial regulations, etc.
In generally I would say, for a correct ideal application reqirement responsible for unit testing should write the unit level spec and tester should implement test based on this spec.
For "have_many and respond_to" I am afraid I have no background info how they are implemented, so can not answer.
I've found some value in writing tests for the presence of database columns. Here's why:
1) Writing them keeps me in the rhythm of TDD.
2) Migrations are usually pretty awesome, until they aren't. I know you're not supposed to edit an existing migration, but when I'm just working on something myself I sometimes do it anyway. And if someone else is working on the same application and changes an existing migration instead of writing a new one, these tests have isolated the problem pretty quickly for me.
If you get bogged down with too many column names & types you can do something like this to save yourself typing:
describe User do
describe 'database' do
describe 'columns' do
%w[reset_password_sent_at remember_created_at current_sign_in_at
last_sign_in_at confirmed_at confirmation_sent_at
created_at updated_at
].each do |column|
it { should have_db_column(column.to_sym).of_type(:datetime) }
end
end
describe 'indexes' do
%w[confirmation_token email reset_password_token
].each do |index|
it { should have_db_index(index.to_sym).unique(true)}
end
end
end
end
Hope that helps.
I read on this slide about RSpec best practices ( http://blog.bandzarewicz.com/slides/krug-the-perfect-rspec/#19 ) and many other places , that it is best practice to have only one expectation with one "it" . For example :
describe UsersController, '#create' do
# setup spec...
it 'creates a new user' do
should assign_to(:user).with(user)
should set_the_flash
should respond_with(:redirect)
should redirect_to(admin_user_path(user))
end
end
vs.
describe UsersController, '#create' do
# setup spec...
it { should assign_to(:user).with(user) }
it { should set_the_flash }
it { should respond_with(:redirect) }
it { should redirect_to(admin_user_path(user)) }
end
Why is it best practise to have only one expectation with one "it" ?
Because this approach is better for documentation. Try rspec --format documentation. And another reason, with one should per it, you can always see which test is failing.