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".)
Related
And it does not work. Can you help me how to write the right test for its case? Thanks very much.
#model.rb
def driver_iq_api
if Rails.env.production?
'https://admin.sss/xmlpost.cfm'
else
'https://eeem/ws/xmlpost.cfm'
end
end
model_spec.rb
describe 'private methods' do
context '.driver_iq_api' do
it 'production true' do
allow(Rails.env).to receive(:production?) {true}.and_return('https://admin.sss/xmlpost.cfm')
end
it 'production false' do
allow(Rails.env).to receive(:production?) {false}.and_return('https://eeem/ws/xmlpost.cfm')
end
end
end
Setting Rails.env to something other than test, inside a test, is a bad idea. Whilst you may "get away with it" in this case, it could cause all sorts of weird side-effects in general, such as writing data to a non-test database.
In addition, it seems you're writing unit tests for private methods, which is typically a bad idea. You should only normally test the public interface of a class.
As stated above, this sort of config should ideally live in a configuration file, such as e.g. application.yml.
The other answer already shows how you could stub the behaviour, but as yet another alternative, you could consider injecting the environment as a method dependency:
def driver_iq_api(env: Rails.env)
if env.production?
'https://admin.sss/xmlpost.cfm'
else
'https://eeem/ws/xmlpost.cfm'
end
end
describe '#driver_iq_api' do
it 'production env' do
expect(model.driver_iq_api(env: 'production'.inquiry)).to eq 'https://admin.sss/xmlpost.cfm'
end
it 'test env' do
expect(model.driver_iq_api(env: 'test'.inquiry)).to eq 'https://eeem/ws/xmlpost.cfm'
end
end
Note that for example, 'test'.inquiry returns a ActiveSupport::StringInquirer instance - which is the same behaviour as calling Rails.env.
...But to reiterate my original point, I wouldn't bother testing this method at all.
As for you test, I'm not familiar with the receive...{block} syntax you use, and I doubt seriously that tests what you think it is testing.
Here is a test suite that tests much more succinctly, and is super easy for anyone to read:
describe '.driver_iq_api' do
subject { MyModel.driver_iq_api }
context 'when in production' do
allow(Rails.env).to receive(:production?).and_return(true)
it { is_expected.to eq 'https://admin.sss/xmlpost.cfm' }
end
context 'when not in production' do
it { is_expected.to eq 'https://eeem/ws/xmlpost.cfm' }
end
end
And to reinforce what #Tom Lord said, this approach is dangerous for methods that actually do things like write to databases and such. I'd use this only for the type of method you have in your example...returning a resource name, boolean, etc. based on env. If your platform-sensitive code is buried deep in a method and can't be tested in isolation, then refactor it out of the big method into an atomic method that can easily be tested (and mocked!).
I agree you should pull this out into config since it is static, but to answer your question:
it "when production" do
allow(Rails.env).to receive(:production?).and_return(true)
expect(my_class.send(:driver_iq_api)).to eq('https://admin.sss/xmlpost.cfm')
end
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.
I'm building a Rails application and formulating tests using RSpec.
I wrote tests for a method I'm creating called current_link_to. This method is supposed to check whether the current page corresponds to the path I pass it and add the current class to the generated link in case it does.
Here is the spec:
require "spec_helper"
describe ApplicationHelper do
describe "#current_link_to" do
let(:name) { "Products" }
let(:path) { products_path }
let(:rendered) { current_link_to(name, path) }
context "when the given path is the current path" do
before { visit(path) }
it "should return a link with the current class" do
# Uses the gem "rspec-html-matchers" (https://github.com/kucaahbe/rspec-html-matchers)
expect(rendered).to have_tag("a", with: { href: path, class: "current" }) do
with_text(name)
end
end
end
context "when the given path is not the current path" do
before { visit(about_path) }
it "should return a link without the current class" do
expect(rendered).to have_tag("a", with: { href: path }, without: { class: "current" } ) do
with_text(name)
end
end
end
end
end
I then tried implementing my method following the spec:
module ApplicationHelper
def current_link_to(name, path, options={})
options.merge!({ class: "#{options[:class]} current".strip }) if current_page?(path)
link_to(name, path, options)
end
end
However, the tests fail with the following error:
Failure/Error: let(:rendered) { current_link_to(name, path) }
RuntimeError: You cannot use helpers that need to determine the current page unless your view context provides a Request object in a #request method
Since I don't really need the current_page? helper method to perform checks on the request, I decided that it would make sense to stub it.
I tried the following methods, but none of them worked:
helper.double(:current_page? => true)
Seems to stub the helper.current_page? method, but it's not the same method that's being called by my function.
allow(ActionView::Helpers::UrlHelper).to receive(:current_page?).and_return(true)
The stub seems not to be effective at all
While writing this question I stumbled onto the solution. I managed to stub the current_page? method using this in a before block:
allow(self).to receive(:current_page?).and_return(true)
It worked, however this solution raised more questions than it really answered. I am now baffled over how this works, as it seems weird that self in a before block would respond to current_page? and that said method would in fact be exactly the same one my helper is calling.
Even after reading documentation and trying to figure out how this works by littering my code with puts calls, the following doubts still haunt me:
Why are helper methods available directly in the specs, when the RSpec docs mention that they should instead be available as methods on the helper object available in all helper specs?
How does stubbing the current_page? method on self in a RSpec before block somehow reflect onto the actual method that gets called by my helper? Does self in my helper for some reason reference the same self you can find in the before block? Is RSpec or Rails including and mixing stuff under the covers?
If the same self encompasses my spec and my helpers, what exactly does self refer to in this case and why is it the same everywhere?
It would be great if someone could help me figure this out because this is blowing my mind up, and I'm scared of using code that I don't really understand.
With respect, you're testing a little too much functionality here. The trick is to test only the bits you need to test.
In this instance, you only need to test that the current class is added when it needs to be, and isn't when it doesn't need to be.
This code should do the trick for you:
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
RSpec.describe ApplicationHelper, type: :helper do
describe 'current_link_to' do
let(:subject) { helper.current_link_to('some_name', 'some_path', options = {}) }
context 'where the path is current' do
before do
allow(helper).to receive(:current_page?).and_return true
end
it 'should include the current class' do
expect(subject).to match /current/
end
end
context 'where the path is not current' do
before do
allow(helper).to receive(:current_page?).and_return false
end
it 'should not include the current class' do
expect(subject).to_not match /current/
end
end
end
end
I've been a little glib and only tested for the presence of 'current' in the returned string. You could test for something like 'class="current"' if you want to be more precise.
The other key is the comment at the top of the page, which Rails inserts into blank helper specs for you:
# Specs in this file have access to a helper object that includes
# the ApplicationHelper.
That means that you can use 'helper' where in your comment above you were using 'self', which makes things a little clearer (imho)
Hope it helps!
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
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.