问题
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.
回答1:
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
回答2:
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
回答3:
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 thesend
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
来源:https://stackoverflow.com/questions/26352251/how-to-reuse-code-in-capybara