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.
Related
There is the following spec:
require 'spec_helper'
describe Place do
let(:place) { FactoryGirl.create(:place) }
subject { place }
it { expect be_valid }
describe 'when content is not present' do
before { place.content = nil }
it { expect be_valid }
end
end
Also there is validation for presence content in Place model. But this spec doesn't throw any exception, even if the last instruction sets 'content' as nil. What's the trouble? Thanks.
You are just setting an expectation but never calling anything.
Replace your expect be_valid calls with
it { expect(subject).to be_valid }
...
it { expect(place).to be_valid }
I guess you are coming from the old should syntax. There you could have written it the way you did:
it { should be_valid }
But the newer expect syntax behaves slightly different
You have let(:place) { FactoryGirl.create(:place) }, so when you reference place for the first time then it creates an instance (with valid content). You are then changing content, but validation is not performed on it.
There are a few ways for doing what you need:
Use the factory that you already have, but instead of create use build
Define an invalid factory at FactoryGirl:
factory :place do
content 'valid value'
factory :invalid_place do
content nil
end
end
3) Or you can use attributes_for
I'm refactoring my model rspecs as to be "as DRY" as possible, leading to something like
require 'spec_helper'
describe Model do
subject { build(:model) }
it { should be_valid }
it { should validate_presence_of(:description) }
it { should ensure_length_of(:description).is_at_least(3).is_at_most(255) }
it { should validate_presence_of(:position) }
it { should validate_numericality_of(:position).is_greater_than_or_equal_to(1) }
end
Now, every file starts with
subject { build(:model) }
it { should be_valid }
so, you guess it, I would like to get rid of these two lines as well...
Any suggestions?
The it { should be_valid } test seems to be testing only your factory. It's not really important to the function of the Model. Consider moving these tests to a single factories_spec test if you'd like to test them. See: https://github.com/thoughtbot/suspenders/blob/master/templates/factories_spec.rb
The matchers you are using in your example don't really require a model built with FactoryGirl. They will work fine with the implicit, default subject (Model.new). When that's not the case, I'd suggest defining as much of the state of your test as possible inside the test -- that is, inside the it blocks. If that results in some duplication, so be it. Particularly costly duplication can be extracted to method calls, which are preferable to subject, let and before because there's no magic to them. As a developer coming back to the project in 6 months, looking at spec on line 75, you'll know exactly what the setup is.
See: http://robots.thoughtbot.com/lets-not
You can use rspec shared examples:
shared_examples "a model" do
subject { build described_class }
it { should be_valid }
end
describe Foo do
it_behaves_like "a model"
end
describe Bar do
it_behaves_like "a model"
end
In my suite I have this in many it blocks:
let(:user) { create(:user) }
let(:plan) { Plan.first }
let(:subscription) { build(:subscription, user: user ) }
it "something" do
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
login_as user
end
How could I DRY this up so I don't have to duplicate all these lines across many files?
You can also create a method like
def prepare_subscription
subscription.create_stripe_customer
subscription.update_card valid_card_data
subscription.change_plan_to plan
end
And in your it block like so:
it "something" do
prepare_subscription
login_as user
end
You ain't checking value for that spec so it always green.
If you need prepare some data before test then you could put that code into helper and call it when needed in (for example) before block.
If you need check spec passing again and again then you could use shared examples.
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.
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.