rspec shared_examples cannot be nested - ruby-on-rails

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

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

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

how can use hash variables with rspec capybara test specification

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.

RSpec - How to create helper method available to tests that will automatically embed "it" tests

I am new to ruby/rails/rspec etc.
Using rspec 2.13.1, I want to create a module with a method that can be called from my tests resulting to subsequent calls of the "it" method of the RSpec::Core::ExampleGroup.
My module:
require 'spec_helper'
module TestHelper
def invalid_without(symbols)
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
end
The code above was added to:
spec/support/test_helper.rb
and in my spec_helper.rb, in the RSpec.configure block, I added the following:
config.include TestHelper
Now, in a test, I do the following:
describe Foo
context "when invalid" do
invalid_without [:name, :surname]
end
end
Running this, I get:
undefined method `invalid_without' for #<Class:0x007fdaf1821030> (NoMethodError)
Any help appreciated..
Use shared example group.
shared_examples_for "a valid array" do |symbols|
symbols = symbols.is_a?(Array) ? symbols : [symbols]
symbols.each do |symbol|
it "should not be valid without #{symbol.to_s.humanize}" do
# Gonna nullify the subject's 'symbol' attribute here
# and expect to have error on it
end
end
end
describe Foo do
it_should_behave_like "a valid array", [:name, :surname]
end

Net.tutorialplus.com -Rspec tutorial- NoMethodError

I am following Rspec testing tutorial on Net.Tutsplus.com.
I've found problem I couldn't solve. Here the thing.
When I run test:
C:\projekt>rspec spec/library_spec.rb --format nested
I get:
C:/projekt/spec/library_spec.rb:35:in `block (3 levels) in <top (required)>': un
defined method `books' for nil:NilClass (NoMethodError)
library_spec.rb looks like that:
require "spec_helper"
describe "Library Object" do
before :all do
lib_arr = [
Book.new("JavaScript: The Good Parts", "Douglas Crockford", :development),
Book.new("Designing with Web Standarts", "Jeffrey Zeldman", :design),
Book.new("Don't Make me Think", "Steve Krug", :usability),
Book.new("JavaScript Patterns", "Stoyan Sefanov", :development),
Book.new("Responsive Web Design", "Ethan Marcotte", :design)
]
File.open "books.yml", "w" do |f|
f.write YAML::dump lib_arr
end
end
before :each do
#lib = Library.new "books.yml"
end
describe "#new" do
context "with no parameters" do
it "has no books" do
lib = Library.new
lib.books.length.should == 0
end
end
context "with a yaml file name parameters " do
it "has five books"
#lib.books.length.should == 5
end
end
end
Due to tutorial instructions I changed library.rb to:
require 'yaml'
class Library
attr_accessor :books
def initalize lib_file = false
#lib_file = lib_file
#books = #lib_file ? YAML::load(File.read(#lib_file)) : []
end
end
According to tutorial it should solve "books-NoMethodError" problem but it still apper.
Where is the problem?
Thanks for help!
undefined method books for nil:NilClass (NoMethodError) just means that you are calling a method books on something that is nil, in this case #lib.
You need to place the before(:each) hook that defines #lib inside a context or describe block, in your code it is not available in the describe '#new' block.
Also, you were missing a do after defining the it "has five books" spec.
I've corrected these errors below:
describe "#new" do
before :each do
#lib = Library.new "books.yml"
end
context "with no parameters" do
it "has no books" do
lib = Library.new
lib.books.length.should == 0
end
end
context "with a yaml file name parameters " do
it "has five books" do
#lib.books.length.should == 5
end
end
end

Resources