Reusing RSpec tests in other tests - ruby-on-rails

I'm very rigorous when it comes to my HTML markup and I follow a strict coding convention for forms, lists, etc...
I would like to include reusable test in my RSpec tests that would allow for me call a form test from any other test and target it directly to the page or URL that I'm testing.
Something like this:
# spec/helpers/form_tester.rb
describe FormTester
it "should check to see if all the text fields have an ID prefix of 'input-'" do
... #form should be valid ...
should be true
end
end
# spec/requests/user_form.rb
describe UserForm
it "should validate the form" do
#form = find(:tag,'form')
# call the FormTester method
end
end
Any ideas on how todo this? I'm using Rails 3.1, with RSpec, Capybara and FactoryGirl.

Use shared examples. In you case, something like this may work:
# spec/shared_examples_for_form.rb
shared_examples 'a form' do
describe 'validation' do
it 'should be validated' do
form.should_be valid
end
end
end
# spec/requests/user_form.rb
describe UserForm
it_behaves_like 'a form' do
let(:form) { find(:tag, 'form') }
end
end
It's also possible to pass parameters to shared examples and place the shared examples inside spec/support. Just have a read at the documentation.

Shared examples are great, but you've got at least two serious problems here.
First: why are you giving your form fields IDs at all? You've already got perfectly good selectors: just use input[name='whatever']. And even if you are giving them IDs, don't put a prefix on them: input#whatever or just #whatever is probably a more sensible selector in your CSS than #input-whatever. By being overspecific on your selector names, you're most likely making your CSS and JavaScript harder to write than they have to be.
Second: don't use RSpec to test your views. RSpec is at its best when confined to models. Cucumber is better for anything user-facing.

Related

How to avoid Rspec shared examples 'previously defined' warning?

I am trying to learn how to use Rspec's shared examples feature and am getting a warning when I run my tests:
WARNING: Shared example group 'required attributes' has been previously defined at:
/Users/me/app/spec/support/shared_examples/required_attributes_spec.rb:1
...and you are now defining it at:
/Users/me/app/spec/support/shared_examples/required_attributes_spec.rb:1
The new definition will overwrite the original one.
....
I have read what I think is the documentation on this problem here but I'm having trouble understanding it/seeing the takeaways for my case.
Here is my shared example:
# spec/support/shared_examples/required_attributes_spec.rb
shared_examples_for 'required attributes' do |arr|
arr.each do |meth|
it "is invalid without #{meth}" do
subject.send("#{meth}=", nil)
subject.valid?
expect(subject.errors[meth]).to eq(["can't be blank"])
end
end
end
I am trying to use this in a User model and a Company model. Here is what it looks like:
# spec/models/user_spec.rb
require 'rails_helper'
describe User do
subject { build(:user) }
include_examples 'required attributes', [:name]
end
# spec/models/company_spec.rb
require 'rails_helper'
describe Company do
subject { build(:company) }
include_examples 'required attributes', [:logo]
end
Per the recommendations in the Rspec docs I linked to above, I have tried changing include_examples to it_behaves_like, but that didn't help. I also commented out company_spec.rb entirely so there was just one spec using the shared example, and I am still getting the warning.
Can anyone help me see what's really going on here and what I should do in this case to avoid the warning?
I found the answer in this issue at the Rspec Github:
Just in case someone googles and lands here. If putting your file with
shared examples into support folder has not fixed the following
error...Make sure your filename does not end with _spec.rb.
As a followup to this, I had the issue in an included shared context with a filename that did not end in _spec.rb and was manually loaded via require_relative, not autoloaded. In my case, the issue was a copy-paste problem. The test looked like this:
RSpec.shared_examples 'foo' do
RSpec.shared_examples 'bar' do
it ... do... end
it ... do... end
# etc.
end
context 'first "foo" scenario' do
let ...
include_examples 'bar'
end
context 'second "foo" scenario' do
let ...
include_examples 'bar'
end
end
The intent was to provide a single shared set of examples that exercised multiple contexts of operation for good coverage, all running through that internal shared examples list.
The bug was simple but subtle. Since I have RSpec monkey patching turned off (disable_monkey_patching!), I have to use RSpec.<foo> at the top level. But inside any other RSpec blocks, using RSpec.<foo> defines the entity inside RSpec's top-level :main context. So, that second set of shared, "internal" examples weren't being defined inside 'foo', they were being defined at the top level. This confused things enough to trigger the RSpec warning as soon more than one other file require_relative'd the above code.
The fix was to just do shared_examples 'bar' in the nested set, not RSpec.shared_examples 'bar', so that the inner examples were correctly described inside the inner context.
(Aside: This is an interesting example of how having monkey patching turned off is more important than might at first glance appear to be the case - it's not just for namespace purity. It allows for a much cleaner distinction in declarations between "this is top level" and "this is nested".)

Where to place helper specs (specs that test additional things in a Rails app)

Not to be confused by the title, I am not asking where I can place specs that test Rails helpers, but helpers specs that test additional things in a Rails app. For example, a spec that checks whether all translations are in place. Or that FactoryGirl factories are valid.
I know the guys at FactoryGirl recommend putting this check on RSpec's before :suite, but I prefer to leave it aside in a spec file of its own (so that it doesn't get executed when I want to run a single spec, for example).
So, what's the place to put these kind of specs?
I put FactoryGirl's specs in spec/models/*_spec.rb. For example
# spec/models/user_spec.rb
describe User do
describe "factories" do
it { expect(build(:user)).to be_valid }
end
end
For other kinds of specs, say for service objects, I put them in spec/services/*_spec.rb. RSpec will automatically detect them.
Edited
For translations, I test them in view specs. Firstly, set config to raise error on translations are missing.
# config/environments/test.rb
# Raises error for missing translations
config.action_view.raise_on_missing_translations = true
Then in the view spec,
describe "pages/welcome" do
it { expect { render }.not_to raise_error }
end

How to reuse code in Capybara

I have a bunch of codes with repeating structures in a feature test in Rails. I would like to dry up my spec by reusing the structure. Any suggestions?
An example is:
feature "Search page"
subject { page }
it "should display results"
# do something
within "#A" do
should have_content("James")
end
within "#B" do
should have_content("October 2014")
end
# do something else
# code with similar structure
within "#A" do
should have_content("Amy")
end
within "#B" do
should have_content("May 2011")
end
end
At first, I tried to define a custom matcher in RSpec, but when I add within block, it did not seem to work. My guess is within is specific to Capybara, and cannot be used in custom matcher in RSpec.
Why not factor the common code into helper methods in a module. Then you can include that module in your spec_helper.rb file
I usually put common code like user_login in such a module in a file in the spec/support folder
spec_helper.rb
#Load all files in spec/support
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
#some config
config.include LoginHelper
#more config
end
spec/support/login_helper.rb
module LoginHelper
def do_login(username, password)
visit root_path
within("#LoginForm") do
fill_in('username', :with => username)
fill_in('password', :with => password)
click_button('submit')
end
end
end
I don't think you're using within as a matcher, since a matcher would be used after a should, should_not, etc. You can load custom, non-matcher methods into your specs by writing a module and including it in your spec_helper.rb config block, e.g.:
spec/support/my_macros.rb
module MyMacros
def within(tag, &block)
# your code here
end
end
spec/spec_helper.rb
require 'spec/support/my_macros'
...
RSpec.configure do |config|
config.include MyMacros
...
end
I'm using Capybara + Cucumber for end-to-end testing. In the end, I think I've pretty much done what both #hraynaud and #eirikir suggest (directionally speaking) - although the details are different since I'm in the Cucumber context. So, consider this not a whole different idea - but maybe a slightly more complete description and discussion. Also, note that my examples focus on testing results - not navigation and form filling. Since it looked like you were in a testing mindset (given your use of should have_content), I thought this might be of interest.
In general, my approach is:
Wrap Capybara tests in validation helper methods within a module. The motivation for wrapping is (a) to save me from having to remember Capybara syntax and (b) to avoid having to type all those repetitive test statements. Also, it ends up making my tests cleaner and more readable (at least for me).
Create a generic validate method that receives (i) a validation helper method name (as a symbol) and (ii) an array of items each of which is to be passed to the validation helper. The validate method simply iterates over the array of items and calls the validation helper method (using the send method), passing each item along with each call.
Attach the helpers and generic validate method to World (read more about World here) so that they are available throughout my Cucumber tests.
Enjoy testing happiness!
Steps 1-3 happen in a file called form_validation_helpers.rb.
features/support/form_validation_helpers.rb
module FormValidationHelpers
...more methods before
# ============================================================================
# Tests that an element is on the page
# ============================================================================
def is_present(element)
expect(find(element)).to be_truthy
end
# ============================================================================
# Tests for the number of times an element appears on a page
# ============================================================================
def number_of(options={})
page.should have_css(options[:element], count: options[:count])
end
# ============================================================================
# Checks that a page has content
# ============================================================================
def page_has_content(content)
page.should have_content(content)
end
...more methods after
# ============================================================================
# The generic validation method
# ============================================================================
def validate(validation, *items)
items.each do |item|
send(validation, item)
end
end
end
World(FormValidationHelpers)
Step 4 (from above) happens in my step files.
features/step_definitions/sample_steps.rb
Then(/^she sees the organization events content$/) do
validate :number_of,
{element: 'ul#organization-tabs li.active', is: 1}
validate :is_present,
"ul#organization-tabs li#events-tab.active"
validate :page_has_content,
"A Sample Organization that Does Something Good",
"We do all sorts of amazing things that you're sure to love."
end
As you can see from the validate :page_has_content example, I can run the test multiple times by adding the appropriate arguments onto the validate call (since the validate method receives everything after the first argument into an array).
I like having very specific selectors in my tests - so I can be sure I'm testing the right element. But, when I start changing my view files, I start breaking my tests (bad) and I have to go back and fix all the selectors in my tests - wherever they may be. So, I made a bunch of selector helpers and attached them to World the same as above.
features/support/form_selectors_helpers.rb
module FormSelectorsHelper
...more _selector methods before
def event_summary_selector
return 'input#event_summary[type="text"]'
end
...more _selector methods after
end
World(FormSelectorsHelper)
So now, I have only one place where I need to keep my selectors up to date and accurate. Usage is as follows (note that I can pass whatever the validation helper method needs - strings, methods, hashes, arrays, etc.)...
features/step_definitions/more_sample_steps.rb
Then(/^she sees new event form$/) do
validate :is_present,
event_summary_selector,
start_date_input_selector,
start_time_input_selector,
end_time_input_selector
validate :is_absent,
end_date_input_selector
validate :is_unchecked,
all_day_event_checkbox_selector,
use_recur_rule_checkbox_selector
validate :is_disabled,
submit_button_selector
validate :has_text,
{ element: modal_title_bar_selector, text: "Okay, let's create a new event!" }
end
Turning back to your question, I imagine you could end up with something like:
feature "Search page"
subject { page }
it "should display results"
# do something
validate :has_content_within,
[a_selector, "James"],
[b_selector, "October 2014"]
# do something else
validate :has_content_within,
[a_selector, "Amy"],
[b_selector, "May 2011"]
end
Capybara Test Helpers provides a nice way to encapsulate test code when using Capybara + RSpec.
RSpec.feature "Search page", test_helpers: [:search] do
before do
visit search_path
end
it "should display results"
search.filter_by(name: 'James')
search.should.have_result(name: 'James', date: 'October 2014')
search.filter_by(name: 'Amy')
search.should.have_result(name: 'Amy', date: 'May 2011')
end
end
You can then implement your own actions and assertions as needed:
class SearchTestHelper < Capybara::TestHelper
aliases(
name_container: '#A',
date_container: '#B',
)
def filter_by(attrs)
attrs.each { |key, name| ... }
click_link('Search')
end
def have_result(name:, date:)
have(:name_container, text: name)
within(:date_container) { have_content(date) } # equivalent
end
end
You can read the guide here.

How can I test with RSpec that Rails 3.2 ActionMailer is rendering the correct view template?

I am using rspec-rails and I want to test that my mailer is rendering the correct view template.
describe MyMailer do
describe '#notify_customer' do
it 'sends a notification' do
# fire
email = MyMailer.notify_customer.deliver
expect(ActionMailer::Base.deliveries).not_to be_empty
expect(email.from).to include "cs#mycompany.com"
# I would like to test here something like
# ***** HOW ? *****
expect(template_path).to eq("mailers/my_mailer/notify_customer")
end
end
end
Is this a valid approach? Or shall I do something completely different to that?
Update
MyMailer#notify_customer might have some logic (e.g. depending on the locale of the customer) to choose different template under different circumstances. It is more or less similar problem with controllers rendering different view templates under different circumstances. With RSpec you can write
expect(response).to render_template "....."
and it works. I am looking for something similar for the mailers.
I think this is a step closer to the answer above, since it does test for implicit templates.
# IMPORTANT!
# must copy https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/support/helpers/next_instance_of.rb
it 'renders foo_mail' do
allow_next_instance_of(described_class) do |mailer|
allow(mailer).to receive(:render_to_body).and_wrap_original do |m, options|
expect(options[:template]).to eq('foo_mail')
m.call(options)
end
end
body = subject.body.encoded
end
OK, I understand what you're trying to achieve now.
You should be able to test which template is called by setting expectations on your mailer for the mail method having been called with particular arguments.
Try this in your test:
MyMailer.should_receive(:mail).with(hash_including(:template => 'expected_template'))

Best practice for reusing code in Rspec?

I'm writing integration tests using Rspec and Capybara. I've noticed that quite often I have to execute the same bits of code when it comes to testing the creation of activerecord options.
For instance:
it "should create a new instance" do
# I create an instance here
end
it "should do something based on a new instance" do
# I create an instance here
# I click into the record and add a sub record, or something else
end
The problem seems to be that ActiveRecord objects aren't persisted across tests, however Capybara by default maintains the same session in a spec (weirdness).
I could mock these records, but since this is an integration test and some of these records are pretty complicated (they have image attachments and whatnot) it's much simpler to use Capybara and fill out the user-facing forms.
I've tried defining a function that creates a new record, but that doesn't feel right for some reason. What's the best practice for this?
There are a couple different ways to go here. First of all, in both cases, you can group your example blocks under either a describe or context block, like this:
describe "your instance" do
it "..." do
# do stuff here
end
it "..." do
# do other stuff here
end
end
Then, within the describe or context block, you can set up state that can be used in all the examples, like this:
describe "your instance" do
# run before each example block under the describe block
before(:each) do
# I create an instance here
end
it "creates a new instance" do
# do stuff here
end
it "do something based on a new instance" do
# do other stuff here
end
end
As an alternative to the before(:each) block, you can also use let helper, which I find a little more readable. You can see more about it here.
The very best practice for your requirements is to use Factory Girl for creating records from a blueprint which define common attributes and database_cleaner to clean database across different tests/specs.
And never keep state (such as created records) across different specs, it will lead to dependent specs. You could spot this kind of dependencies using the --order rand option of rspec. If your specs fails randomly you have this kind of issue.
Given the title (...reusing code in Rspec) I suggest the reading of RSpec custom matchers in the "Ruby on Rails Tutorial".
Michael Hartl suggests two solutions to duplication in specs:
Define helper methods for common operations (e.g. log in a user)
Define custom matchers
Use these stuff help decoupling the tests from the implementation.
In addition to these I suggest (as Fabio said) to use FactoryGirl.
You could check my sample rails project. You could find there: https://github.com/lucassus/locomotive
how to use factory_girl
some examples of custom matchers and macros (in spec/support)
how to use shared_examples
and finally how to use very nice shoulda-macros
I would use a combination of factory_girl and Rspec's let method:
describe User do
let(:user) { create :user } # 'create' is a factory_girl method, that will save a new user in the test database
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
end
# spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
username { Faker::Internet.user_name }
end
end
This allows you to do great stuff like this:
describe User do
let(:user) { create :user, attributes }
let(:attributes) { Hash.new }
it "should be able to run" do
user.run.should be_true
end
it "should not be able to walk" do
user.walk.should be_false
end
context "when user is admin" do
let(:attributes) { { admin: true } }
it "should be able to walk" do
user.walk.should be_true
end
end
end

Resources