how can use hash variables with rspec capybara test specification - ruby-on-rails

My spec file:
require 'spec_helper'
describe "State Contracts page" do
#state_data = {
:state_slug => 'Alabama',
:state_name => 'California'
}
before(:each) { visit state_path(:state=>"#{#state_data[:state_slug]}" )}
it 'should have header' do
page.should have_content("#{#state_data[:state_name]} Contracts")
end
# show statistics specification for State Contract
it "should have #{#state_data[:state_name]} Statistics details" do
page.should have_content("#{#state_data[:state_name]} Statistics")
page.should have_content('Total Obligated Amount')
page.should have_content('Total Transactions')
page.should have_content('Total Contractors')
page.should have_content('Total Contract Recipients')
page.should have_content('Total Offers')
end
end
# show State link
it "should have visible #{#state_data[:state_name]} Links" do
page.should have_content("#{#state_data[:state_name]} Links")
assert_equal(true, find_link("Agencies in #{#state_data[:state_name]}").visible?)
assert_equal(true, find_link("Contractors in "{#state_data[:state_name]}").visible?)
assert_equal(true, find_link("Contracts in #{#state_data[:state_name]}").visible?)
end
end
After when I run the test, I got next Error:
undefined method `[]' for nil class for "#{#state_data[:state_name]}"
I think i am interpolating hash variable but now not getting right.

You can't use instance variables in an it block without declaring it somewhere in a before. Wrap #state_data in your before(:each) block and it should work.
It would look like the following:
before do
#state_data = {
:state_slug => 'Alabama',
:state_name => 'California'
}
visit state_path(:state=>"#{#state_data[:state_slug]}"
end
My understanding is that using instance variables is considered an antipattern and you should consider using let() or subject() instead
Using let() would change this to:
let(:state_data) do
{
:state_slug => 'Alabama',
:state_name => 'California'
}
end
before { visit state_path(:state=>"#{state_data[:state_slug]}" }
it 'should have header' do
page.should have_content("#{state_data[:state_name]} Contracts")
end

Local variables or instance variables defined in a describe block are not accessible in any contained it blocks.
If you want to make arbitrary variables or methods available across multiple it blocks, you need to use let, let! or before. The let methods let you memoize helper methods while the before method let's you execute arbitrary Ruby code prior to executing the it block. The subject method is also available as means of defining the subject helper.
Of course, you can also define methods or variables within each it block.

Related

RSpec before in a helper

Is it possible to do something like this?
module MyHelper
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
Then in my tests I could do something like:
RSpec.describe 'My cool test' do
include MyHelper
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
EDIT: This produces the following error:
undefined method `before' for MyHelper:Module (NoMethodError)
Essentially I have a case where many tests do different things, but a common model across off of them reacts on an after_commit which ends up always calling a method which talks to an API. I dont want to GLOBALLY allow Class to receive :method as, sometimes, I need to define it myself for special cases... but I'd like to not have to repeat my allow/receive/and_return and instead wrap it in a common helper...
You can create a hook that is triggered via metadata, for example :type => :api:
RSpec.configure do |c|
c.before(:each, :type => :api) do
allow(Class).to receive(:method).and_return(true)
end
end
And in your spec:
RSpec.describe 'My cool test', :type => :api do
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
You can also pass :type => :api to individual it blocks.
It is possible to do things like you want with feature called shared_context
You could create the shared file with code like this
shared_file.rb
shared_context "stubbing :method on Class" do
before { allow(Class).to receive(:method).and_return(true) }
end
Then you could include that context in the files you needed in the blocks you wanted like so
your_spec_file.rb
require 'rails_helper'
require 'shared_file'
RSpec.describe 'My cool test' do
include_context "stubbing :method on Class"
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
And it will be more naturally for RSpec than your included/extended module helpers. It would be "RSpec way" let's say.
You could separate that code into shared_context and include it into example groups (not examples) like this:
RSpec.describe 'My cool test' do
shared_context 'class stub' do
before (:each) do
allow(Class).to receive(:method).and_return(true)
end
end
describe "here I am using it" do
include_context 'class stub'
it 'Tests a Class Method' do
expect { Class.method }.to eq true
end
end
describe "here I am not" do
it 'Tests a Class Method' do
expect { Class.method }.not_to eq true
end
end
end
Shared context can contain let, helper functions & everything you need except examples.
https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context

Getting rid of repetitive rspec tests across contexts

Let's say I have various RSpec context blocks to group tests with similar data scenarios.
feature "User Profile" do
context "user is active" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is pending" do
before(:each) { (some setup) }
# Various tests
...
end
context "user is deactivated" do
before(:each) { (some setup) }
# Various tests
...
end
end
Now I'm adding a new feature and I'd like to add a simple scenario that verifies behavior when I click a certain link on the user's page
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
Ideally I'd love to add this test for all 3 contexts because I want to be sure that it performs correctly under different data scenarios. But the test itself doesn't change from context to context, so it seems repetitive to type it all out 3 times.
What are some alternatives to DRY up this test set? Can I stick the new test in some module or does RSpec have some built in functionality to let me define it once and call it from each context block?
Thanks!
You can use shared_examples ... define them in spec/support/shared_examples.rb
shared_examples "redirect_help" do
it "clicking help redirects to the user's help page" do
click_on foo_button
expect(response).to have('bar')
end
end
Then in each of your contexts just enter...
it_behaves_like "redirect_help"
You can even pass a block to it_behaves_like and then perform that block with the action method, the block being unique to each context.
Your shared_example might look like...
shared_examples "need_sign_in" do
it "redirects to the log in" do
session[:current_user_id] = nil
action
response.should render_template 'sessions/new'
end
end
And in your context you'd call it with the block...
describe "GET index" do
it_behaves_like "need_sign_in" do
let(:action) {get :index}
end
...

Does Rubymine support "let" syntax in rails?

I am using Rubymine to create a project in Rails4, rspec and capybara. When I use the let syntax for defining variables in Capybara features, it seems RubyMine isn't able to detect the existence of the variables. For instance in this code below, the variable capsuleHash, capsuleForm and capsuleViewPage are all not being recognized in intelliJ in the scenario section. Does anyone have a workaround?
require 'spec_helper'
feature 'Capsules Feature' do
let(:capsuleHash) {attributes_for(:tdd_capsule)}
let(:capsuleForm) {CapsuleCreateForm.new}
let(:capsuleViewPage) {CapsuleViewPage.new}
scenario 'Add a new capsule and displays the capsule in view mode' do
visit '/capsules/new'
expect{
capsuleForm.submit_form(capsuleHash)
}.to change(Capsule,:count).by(1)
capsuleViewPage.validate_on_page
expect(page).to have_content capsuleHash[:title]
expect(page).to have_content capsuleHash[:description]
expect(page).to have_content capsuleHash[:study_text]
expect(page).to have_content capsuleHash[:assignment_instructions]
expect(page).to have_content capsuleHash[:guidelines_for_evaluators]
expect(page).to have_link 'Edit'
end
end
I'm not familiar with RubyMine other than it's an IDE for Ruby. The way you phrased your question, though, I'm assuming that you're referring to some feature of RubyMine which displays the "variables" defined at any particular point in a program.
If this is case, the reason that the symbols you've passed to let wouldn't "show up" as variables is because they are not being defined as variables. They are being defined as methods which return the value of the associated block. On the first call from within each it block, the value of the block is remembered and that value returned on subsequent calls within the same block.
Note that there is nothing wrong with the RSpec code in terms of defining those methods. The following code passes, for example:
class Page
def has_content?(content) true ; end
def has_link?(link) true ; end
end
page = Page.new
class CapsuleCreateForm
def submit_form(hash)
Capsule.increment_count
end
end
class CapsuleViewPage
def validate_on_page
end
end
def attributes_for(symbol)
{}
end
def visit(path)
end
class Capsule
##count = 0
def self.count
##count
end
def self.increment_count
##count += 1
end
end
describe 'Capsules Feature' do
let(:capsuleHash) {attributes_for(:tdd_capsule)}
let(:capsuleForm) {CapsuleCreateForm.new}
let(:capsuleViewPage) {CapsuleViewPage.new}
it 'Add a new capsule and displays the capsule in view mode' do
visit '/capsules/new'
puts method(:capsuleHash)
expect{
capsuleForm.submit_form(capsuleHash)
}.to change(Capsule,:count).by(1)
capsuleViewPage.validate_on_page
expect(page).to have_content capsuleHash[:title]
expect(page).to have_content capsuleHash[:description]
expect(page).to have_content capsuleHash[:study_text]
expect(page).to have_content capsuleHash[:assignment_instructions]
expect(page).to have_content capsuleHash[:guidelines_for_evaluators]
expect(page).to have_link 'Edit'
end
end
RubyMine does support let blocks, but you'll need to be sure to use the latest version, 6.0.2. See http://youtrack.jetbrains.com/issue/RUBY-14673

rspec shared_examples cannot be nested

I want to write two tests and both partially rely on the same behavior, approximately as seen below. This is something I would like to pull out of my code, and it seems like shared contexts are how to do it, but there is a scoping problem.
require 'spec_helper'
def getlink()
['link','id']
end
describe 'static pages' do
hash = {'link' => {'id' => 'payload'},'link_' => {'id_' => 'payload_'}}
subject{hash}
shared_examples_for 'it is mapped correctly' do |link, id|
it 'is mapped correctly' do
expect(subject[link]).to have_key(id)
end
end
describe 'the payload is correct' do
it_should_behave_like 'it is mapped correctly', 'link','id'
it 'has the correct value' do
expect(subject['link']['id']).to eq('payload')
end
end
# works fine
describe 'the get link function works correctly' do
it 'links inside the has' do
link = getlink()
expect(subject[link[0]]).to have_key(link[1])
end
end
# fails saying that it_should_behave_like is not defined.
describe 'the get link function works correctly with shared examples' do
it 'links inside the has' do
link = getlink()
it_should_behave_like 'it is mapped correctly', link[0], link[1]
end
end
end
why is this designed to fail? Is there an idiomatic way to accomplish this?
Like other it methods, it_should_behave_like is not defined within other it methods. You can see that you get the same exception when nesting regular its:
require 'rspec/autorun'
describe 'it inside it' do
it 'outer' do
it 'inner' do
end
end
end
#=> 1) it inside it outer
#=> Failure/Error: Unable to find matching line from backtrace
#=> NoMethodError:
#=> undefined method `it' for #<RSpec::Core::ExampleGroup::Nested_1:0x28e1e60>
#=> # stuff.rb:37:in `block (2 levels) in <main>'
To fix the exception, you could simply get rid of the outer it:
describe 'the get link function works correctly with shared examples' do
link = getlink()
it_should_behave_like 'it is mapped correctly', link[0], link[1]
end
If the outer it is being used to describe some information, you could make it a context instead:
describe 'the get link function works correctly with shared examples' do
context 'links inside the has' do
link = getlink()
it_should_behave_like 'it is mapped correctly', link[0], link[1]
end
end

Change the order of before hooks in rspec

I have a controller spec like this :
describe "#create" do
before { post 'create', params }
context "when the artist is valid" do
before { allow(artist).to receive(:save).and_return(true) }
it { expect(page).to redirect_to(root_path) }
it { expect(notifier).to have_received(:notify) }
end
end
This is a simple spec but It doesn't work because the describe's before block is executed before the context's before block. So, the result of artist.save is not stubed when the create action is called.
It tried to do this :
describe "first describe" do
before { puts 2 }
describe "second describe" do
before { puts 1 }
it "simple spec" do
expect(1).to eq 1
end
end
end
I see the "2" before the "1". I'm not sure but I think it was working with previous versions.
I know, I can do this :
describe "#create" do
context "when the artist is valid" do
before { allow(artist).to receive(:save).and_return(true) }
it "redirect to the root path" do
post 'create', params
expect(page).to redirect_to(root_path)
end
it "do notifications" do
post :create, params
expect(notifier).to have_received(:notify)
end
end
end
But I think it's less clean.
I found, on this page, http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/Hooks#before-instance_method than the order should be this :
before(:suite) # declared in RSpec.configure
before(:all) # declared in RSpec.configure
before(:all) # declared in a parent group
before(:all) # declared in the current group
before(:each) # declared in RSpec.configure
before(:each) # declared in a parent group
before(:each) # declared in the current group
It's not the case on this example.
I'm not sure but I think it was working with older versions of rspec.
Is there a solution?
I would strongly recommend against you changing the order of hooks in rspec. That will make your app non-standard and Rails is build on standards and having things work as expected.
Everything you're describing it "as designed". Outer before blocks are always called before inner blocks.
Your example that you feel is "less clean" is the standard way to do controller specs. I actually encourage you to do it this way so that it is more maintainable/readable. It does not look unclean to me at all.
That said, there are some options:
You can use a method. I have more than once had a method that was do_post or something similar
You can use a let block which is initialized lazily. I would find it unlcean if it relied on other before blocks running first, but it's an option.
You can define subject. https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/subject/explicit-subject

Resources