rspec Let() behavior in helper specs - ruby-on-rails

I have the following in my application_helper_spec.rb
shared_examples_for "some shared process" do
it "displays the value defined by Let" do
puts object.inspect # This should output the value defined by Let
end
end
describe ApplicationHelper do
describe "a_helper_methond" do
describe "some behaviour" do
let(:object) { "foo" }
puts object.inspect # This should output the value defined by Let
it_should_behave_like "some shared process"
end
end
end
However, when I run this I get two errors on both puts:
undefined local variable or method `object' for #<Class:0x007f951a3a7b20> (NameError)
Why? I have the exact same code in my model and request specs at it runs fine, but in the helper specs it doesn't.

The let call defines a method in the context your specs will be executed. Your puts statement however is outside that scope. You need to wrap it inside an it block.
it 'print debug output' do
puts object.inspect
end

I also further improved this by putting the Let command inside the it_behaves_like block

Related

Is there a "not" equivalent in rspec, for e.g. the logical not for "and_return"

Couldn't find the method in the rspec docs, but is there an alternative to do this?
allow_any_instance_of(<some connection>).to receive(<post method>).and_return(200)
the above code block to not return 200 instead
You have fundamentally misunderstood what allow_any_instance_of and to_return do.
allow_any_instance_of is used to stub a method on any instance of a given class. It does not set any expectations - expect_any_instance_of does.
class Foo
def bar(*args)
"baz"
end
end
RSpec.describe Foo do
describe "allow_any_instance_of" do
it "does not create an expectation" do
allow_any_instance_of(Foo).to receive(:bar).and_call_original
expect(true).to be_truthy
end
end
describe "expect_any_instance_of" do
it "sets an expectation" do
expect_any_instance_of(Foo).to receive(:bar).and_call_original
expect(Foo.new.bar).to eq 'baz'
end
# this example will fail
it "fails if expected call is not sent" do
expect_any_instance_of(Foo).to receive(:bar).and_call_original
expect(true).to be_truthy
end
end
end
.and_return is used to set the return value of a mock/stub. It does not as you seem to believe set an expectation on the return value.
RSpec.describe Foo do
describe "and_return" do
it "changes the return value" do
allow_any_instance_of(Foo).to receive(:bar).and_return('hello world')
expect(Foo.new.bar).to_not eq 'baz'
expect(Foo.new.bar).to eq 'hello world'
end
end
end
You can use .and_call_original when you want to spy on a method without changing its return value. By default any method stubbed with allow_any_instance_of/expect_any_instance will return nil.
AFAIK its not possible to set an expectation on what the return value of .and_call_original is. Thats one of reasons why any_instance_of is considered a code smell and should be avoided.

Testing Rails helper with Rspec: undefined local variable or method `request'

I have a helper method that uses 'request' to determine the URL. However, rspec can't seem to find request. I thought request was available to all front-facing tests?
How can I account for the request method in my spec?
Helper Spec
require 'spec_helper'
describe ApplicationHelper do
describe "full_title" do
it "should include the page title" do
expect(full_title("help")).to include('help')
end
end
end
Helper methods
def full_title(page_title)
if staging? # causing the issue
base_title = "Staging"
else
base_title = "Company Name"
end
if page_title.empty?
"#{base_title} | Tag line "
else
"#{base_title} | #{page_title} "
end
end
def staging? # the request here seems to be the problem
request.original_url.include? "staging"
end
Rspec error
Failure/Error: expect(full_title("help")).to include('help')
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::ApplicationHelper_2::FullTitle:0x00000106260078>
Thanks in advance.
First off: request is only available in the controller tests (and even then only in the request specs I think), helper tests are really basic and isolated. Which is good. Your helper code should be really minimal and normally only work on the input it receives.
However this is pretty easily solvable by using stubbing.
So write something like
#note, OP needed to replace 'helper' with 'self'for Rails 4.0.0 and Rspec 3.0
require 'rails_helper'
describe ApplicationHelper do
describe "full_title" do
context "in staging" do
it "should include the page title" do
helper.should_receive(:staging?).and_return(true)
expect(full_title("help")).to include('help')
end
end
context "not in staging" do
it "should include the page title" do
helper.should_receive(:staging?).and_return(false)
expect(full_title("help")).to include('help')
end
end
end
end
Which is imho a very clear, and then you write separate tests for your staging? method:
describe "staging?" do
context "when in staging" do
it "returns true" do
helper.stub(:request) { OpenStruct.new(original_url: 'staging') }
expect( helper.staging? ).to be true
end
end
context "when not in staging" do
it "returns false" do
helper.stub(:request) { OpenStruct.new(original_url: 'development') }
expect(helper.staging?).to be false
end
end
end
end
Some small remarks: ruby default indentation is 2 spaces.
Secondly, your function now literally says return true if true, ideally it should be written like
def staging?
request.original_url.include? "staging"
end

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.

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

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

Resources