I am new to rspec and we are using it for test automation for our application. I have three hooks that are reused in every rspec test. The hooks call a number modules that launch are used to launch & log into the site plus some other usable methods.
I have created a hooks.rb file and placed those hooks in there and call it within the rspec test but now I have lost the ability to call the instance variables that relate to the methods I need. I could globalize the variables but I have read its not a good idea to do so.
Does anyone have any insight on what the best approach would be?
I am including code example.
before(:all) do
< this before hook include making files available and initialization browser >
** very section
end
after(:all) do
#client.quit
end
before(:each) do
#page.goto
end
** I have to include this in every spec file and would not like to have to. One call to the location so it is included for any spec.
Thank you,
Joe
So, my spec_helper.rb consist of the following information:
RSpec.configure do |config|
config.before(:all) do
require_relative '../../lib/env'
filedir = File.expand_path('../../etc',File.dirname(__FILE__))
config = Matr::Configuration.new("#{filedir}/config.yaml", ENV["M_ENV"] || "development")
#client = config.construct_selenium_driver "Insight: Login"
#client.window.resize_to(1280,720)
#page = Matr::Pages::Insight::LoginPage.new(#client)
#account_page = Matr::Pages::Insight::InsightPage.new(#client)
#choose_account = Matr::Pages::Insight::ChooseAccount.new(#client)
#logout = Matr::Pages::Insight::LogOut.new(#client)
#nav_bar = Matr::Pages::Insight::NavBar.new(#client)
#db = Matr::Models::DB.new
#manage_campaigns = Matr::Pages::Insight::ManageCampaignsPage.new(#client)
end
end
You can define hooks for all examples when you configure rspec in your spec_helper.rb
For example you could do
RSpec.configure do |config|
config.before(:all) do
...
end
end
If you don't actually need this for all specs then you can use rspec's metadata filters. For example, to apply to all of the request specs you'd do
config.before(:all, type: :request) do
...
end
To only run against the specs with type :request. You can also make up your own metadata keys, so if you did
config.before(:all, browser: true) do
...
end
Then that before(:all) would run for example groups created in this way:
describe "something", browser: true do
...
end
Have you considered using shared examples? Generally, you should not have to roll your own approach to creating reusable modules.
Related
Hi i am working with a RoR project with ruby-2.5.0 and rails 5. I am using AWS SQS. I have created a job as follows:-
class ReceiptsProcessingJob < ActiveJob::Base
queue_as 'abc'
def perform(receipt_id)
StoreParserInteractor.process_reciept(receipt_id)
end
end
Now i want to write unit test for it. I tried like:-
# frozen_string_literal: true
require 'rails_helper'
describe ReceiptsProcessingJob do
describe "#perform_later" do
it "scan a receipt" do
ActiveJob::Base.queue_adapter = :test
expect {
ReceiptsProcessingJob.perform_later(1)
}.to have_enqueued_job
end
end
end
But it doesnot cover StoreParserInteractor.process_reciept(receipt_id). Please help how can i cover this. Thanks in advance.
The example is testing the job class. You need to write a spec for StoreParserInteractor and test the method process_reciept.
Something along the lines of (pseudo code):
describe StoreParserInteractor do
describe "#process_receipt" do
it "does that" do
result = StoreParserInteractor.process_receipt(your_data_here)
expect(result to be something)...
end
end
end
But, the Rails guide suggests this kind of test:
assert_enqueued_with(job: ReceiptsProcessingJob) do
StoreParserInteractor.process_reciept(receipt_id)
end
Maybe this increases code coverage as well.
In my opinion, you shouldn't actually test the ActiveJob itself, but the logic behind it.
You should write a test for StoreParserInteractor#process_reciept. Think of ActiveJob as an "external framework" and it is not your responsibility to test the internals of it (e.g. if the job was enqueued or not).
As kitschmaster said, don't test ActiveJob classes, in short
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".)
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
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 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