RSpec let Scope - ruby-on-rails

How is RSpec let scoping defined? I have a test that uses shared_examples and I think the let's are leaking. I thought this would work but I'm missing some understanding if anyone can explain why foo is only defined as 123 here.
# test.rb
RSpec.describe "Test" do
let(:foo) { '123' }
include_examples "when missing foo"
include_examples "when foo is valid"
end
# shared_examples.rb
shared_examples "when missing foo" do
let(:foo) { nil }
it "expects foo to be nil" do
expect(foo).to be_nil
end
end
shared_examples "when foo is valid" do
let(:foo) { 123 }
it "expects foo to not be nil" do
expect(foo).to eq(123)
end
end
Test result:
Test
expects foo to not be nil
expects foo to be nil (FAILED - 1)
Failures:
1) Test expects foo to be nil
Failure/Error: expect(foo).to be_nil
expected: nil
got: 123

let is a shorthand for a memoized method definition think
def foo
#foo ||= '123'
end
include_examples just merges the shared_examples into the current context so your foo definition is being overwritten e.g. Your example this becomes:
RSpec.describe "Test" do
let(:foo) {'123' }
let(:foo) { nil }
let(:foo) { 123 }
it "expects foo to be nil" do
expect(foo).to be_nil
end
it "expects foo to not be nil" do
expect(foo).to eq(123)
end
end
Which is why the test fails. If you were to reverse the order of the include examples you will note the other test would be the failing one.
That being said you can use it_behaves_like instead, which will create a nested context and your foo definitions will be isolated to that context:
RSpec.describe "Test" do
let(:foo) { '123' }
it_behaves_like "when missing foo"
it_behaves_like "when foo is valid"
end
In this case your foo will be wrapped in its own context and both tests will pass.
This is further explained in the RSpec Documentation for shared_examples

let is not scoped to a shared example as its not an example group.
If you want to redefine foo per shared example you would do it like so:
RSpec.describe "Test" do
let(:foo) { '123' }
include_examples "when missing foo" do
let(:foo) { nil }
end
include_examples "when foo is valid" do
let(:foo) { 123 }
end
end
# shared_examples.rb
shared_examples "when missing foo" do
it "expects foo to be nil" do
expect(foo).to be_nil
end
end
shared_examples "when foo is valid" do
it "expects foo to not be nil" do
expect(foo).to eq(123)
end
end
Or you would just define foo as a parameter instead of using let:
shared_examples "when missing foo" do |foo|
it "expects foo to be nil" do
expect(foo).to be_nil
end
end
include_examples "when missing foo", foo: nil

Related

Is there an inverse of it_behaves_like in Rspec?

Is there something to the effect of 'it_doesnt_behave_like' or 'group_will_fail_with(SomeError)'? I don't see anything in the docs, but I feel like this would be desirable functionality. E.g.
shared_examples_for "a foo doer for admin" do
it "does this" do
...
end
it "does that" do
...
end
end
context "when logged in as an admin" do
let(:admin) { FactoryGirl :admin }
before(:each) { sign_in_as admin }
it_behaves_like "a foo doer for admin"
end
context "when logged in as a user" do
let(:user) { FactoryGirl :user }
before(:each) { sign_in_as user }
# is there something like this?
group_will_fail_with("a foo doer for admin", SomeError) / it_behaves_not_like "a foo maker" / etc.
end
If this is an anti-pattern or there is something wrong with my thinking all corrections are appreciated.

Rspec shared setting variables for many test

Tests are set by Rspec + factory girl, and I have around 20 Rspec test files, all of them share some of the required setting variables. Eg:
let!(:event) { create(:event) }
let!(:user) { create(:user) }
let!(:user) { create(:ticket) }
I don't want to copy paste these variables assignment for each test file, Is there a cleaner way to populate the vars? Thanks in advance.
If yo're using let, you don't need to repeat them. It will persist through the current describe/context block.
eg the following should all pass:
describe 'some stuff' do
let!(:event) { create(:event) }
it 'should get an event' do
expect(event).to_not be_nil
end
describe 'indented stuff' do
let!(:user) { create(:user) }
it 'should still get an event' do
expect(event).to_not be_nil
end
it 'should get a user' do
expect(user).to_not be_nil
end
end
end
To share setup amongst more than one file, define a method in rails_helper/spec_helper:
module CommonSetup
def setup_vars
let!(:event) { create(:event) }
let!(:user) { create(:user) }
let!(:user) { create(:ticket) }
end
end
...
...
Rspec.configure do |c|
...
c.extend CommonSetup
...
end
And then in your spec just call setup_vars in your spec

Rspec: helper method to detect presence of form field

I want to test that my controller helper method honeypot_detected? will be true if the #params of a form field named birth_city is filled in.
Do I need to use mocks in order to test this?
helpers.rb:
def honeypot_detected?
#params[:birth_city].present?
end
helpers_spec.rb
require 'spec_helper'
describe WindowWashers::Controllers::Shared::Helpers do
.
.
.
before(:all) { #controller = ApplicationController.new }
context "when honeypot_detected? is called" do
it "returns true when birth_city is storing a value" do
#Not sure how to represent :birth_city => 'Dallas
expect(honeypot_detected?).to be_true
end
end
end
.
.
.
end
context "when honeypot_detected? is called" do
it "returns true when birth_city is storing a value" do
instance_variable_set(:#params, {:birth_city => "Dallas"})
expect(honeypot_detected?).to be_true
end
end
Since you're checking values stored in an instance variable, you should be able to use assign to set it. I'm assuming your instance variable #params is just a hash, in which case you probably don't need to go so far as to use a test double like you might for a more complicated object:
describe '#honeypot_detected?' do
let(:honeypot_detected) { helper.honeypot_detected? }
context 'when birth_city present in params' do
before { assign(:params, { birth_city: "Dallas" }) }
it 'returns true' do
expect(honeypot_detected).to be_true
end
end
context 'when birth_city absent from params' do
before { assign(:params, { foo: "bar" }) }
it 'returns false' do
expect(honeypot_detected).to be_false
end
end
end

Rspec before block scope inconsistant

I'm having scope issues when using RSpec's `before(:all)' block.
Previously I was using before(:each), which worked fine:
module ExampleModule
describe ExampleClass
before(:each) do
#loader = Loader.new
end
...
context 'When something' do
before(:each) do
puts #loader.inspect # Loader exists
# Do something using #loader
end
...
end
end
end
But switching the nested before(:each) block tobefore(:all) means loader is nil:
module ExampleModule
describe ExampleClass
before(:each) do
#loader = Loader.new
end
...
context 'When something' do
before(:all) do
puts #loader.inspect # Loader is nil
# Do something using #loader
end
...
end
end
end
So why is #loader nil in the before(:all) block, but not in the before(:each) block?
All the :all blocks happen before any of the :each blocks:
describe "Foo" do
before :all do
puts "global before :all"
end
before :each do
puts "global before :each"
end
context "with Bar" do
before :all do
puts "context before :all"
end
before :each do
puts "context before :each"
end
it "happens" do
1.should be_true
end
it "or not" do
1.should_not be_false
end
end
end
Output:
rspec -f d -c before.rb
Foo
global before :all
with Bar
context before :all
global before :each
context before :each
happens
global before :each
context before :each
or not
As per the Rspec documentation on hooks, before :all hooks are run prior to before :each.

Reusing RSpec behavior validation

In my Rails 3 application, I have a RSpec spec that checks behavior of a given field (role in the User model) to guarantee that the value is within a list of valid values.
Now I am going to have the exact same spec for another field, in another model with another set of valid values. I would like to extract the common code instead of merely copying and pasting it, changing the variables.
I am wondering if this would be the case to use a shared example or other RSpec reuse technique.
Here's the relevant RSpec code:
describe "validation" do
describe "#role" do
context "with a valid role value" do
it "is valid" do
User::ROLES.each do |role|
build(:user, :role => role).should be_valid
end
end
end
context "with an empty role" do
subject { build(:user, :role => nil) }
it "is invalid" do
subject.should_not be_valid
end
it "adds an error message for the role" do
subject.save.should be_false
subject.errors.messages[:role].first.should == "can't be blank"
end
end
context "with an invalid role value" do
subject { build(:user, :role => 'unknown') }
it "is invalid" do
subject.should_not be_valid
end
it "adds an error message for the role" do
subject.save.should be_false
subject.errors.messages[:role].first.should =~ /unknown isn't a valid role/
end
end
end
end
What would be the best case to reuse this code, but extracting role (the field being verified) and User::ROLES (the collection of valid values) into parameters being passed to this code?
I think this is a perfectly reasonable use case for shared examples. e.g. something like this:
shared_examples_for "attribute in collection" do |attr_name, valid_values|
context "with a valid role value" do
it "is valid" do
valid_values.each do |role|
build(:user, attr_name => role).should be_valid
end
end
end
context "with an empty #{attr_name}" do
subject { build(:user, attr_name => nil) }
it "is invalid" do
subject.should_not be_valid
end
it "adds an error message for the #{attr_name}" do
subject.save.should be_false
subject.errors.messages[attr_name].first.should == "can't be blank"
end
end
context "with an invalid #{attr_name} value" do
subject { build(:user, attr_name => 'unknown') }
it "is invalid" do
subject.should_not be_valid
end
it "adds an error message for the #{attr_name}" do
subject.save.should be_false
subject.errors.messages[attr_name].first.should =~ /unknown isn't a valid #{attr_name}/
end
end
end
Then you can call it in your specs like this:
describe "validation" do
describe "#role" do
behaves_like "attribute in collection", :role, User::ROLES
end
end
Haven't tested this but I think it should work.
You can DRY your spec with shared_examples technic this way:
shared_examples "no role" do
it "is invalid" do
subject.should_not be_valid
end
end
context "with an empty role" do
subject { Factory.build(:user, :name => nil) }
it_behaves_like "no role"
end
context "with an invalid role value" do
subject { Factory.build(:user, :name => '') }
it_behaves_like "no role"
end
But what about your idea to DRY few specs..I think it's too much. I'm convince that spec has to be readable firstly and only then DRY'ing. If you DRY few specs, it will be probably a headache for future reading/refactoring/changing this code.

Resources