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.
Related
I'm having an issue logging in a user with Devise/Rspec to hit a route for testing. I'm used the support/controller_macros module as outlined by devise, but whenever I try using
login_user in any part of my test I get the error: before is not available from within an example (e.g. an it block) or from constructs that run in the scope of an example (e.g. before, let, etc). It is only available on an example group (e.g. a describe or context block).
I've tried a moving things around, making sure all of my requires are set up correctly, etc.
My test:
require "rails_helper"
RSpec.describe BallotsController, type: :controller do
describe "index" do
it "renders" do
login_user
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
(I've tried adding login_user inside the describe block, and the upper block as well)
My controller_macros:
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user_confirmed]
user = FactoryBot.create(:user_confirmed)
sign_in user
end
end
def login_admin
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
user = FactoryBot.create(:admin)
sign_in user
end
end
end
My spec helper:
require "rails_helper"
require_relative "support/controller_macros"
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.include ControllerMacros, :type => :controller
config.include Devise::Test::ControllerHelpers, :type => :controller
config.example_status_persistence_file_path = "spec/examples.txt"
config.disable_monkey_patching!
if config.files_to_run.one?
config.default_formatter = "doc"
end
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
I expect it to log a user in, and for the controller to correctly hit the index route. Thank you!
Only issue in the code(to resolve the error, without digging into the debate of if this method should be used or not) is that you have login user within the it block, which can't be because it is calling before(:each).
If you see the device documentation, you will also see that it does not have this call in it block, but rather outside of it block in a describe block. Which applies this call to all it blocks in that describe block.
Your code willcbecome:
RSpec.describe BallotsController, type: :controller do
describe "index" do
login_user
it "renders" do
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
The way I prefer:
In your controller_macros, replace the login_user with:
def login_user(user)
#request.env["devise.mapping"] = Devise.mappings[:user_confirmed]
sign_in user
end
Now, wherever you want to login user, you can do it something like:
RSpec.describe BallotsController, type: :controller do
describe "index" do
let(:user) { FactoryBot.create(:user) }
it "renders" do
login_user(user)
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
# OR
describe "index" do
let(:user) { FactoryBot.create(:user) }
before(:each) do
login_user(user)
end
it "renders" do
ballots_path
expect(response).to be_success
expect(response).to render_template("index")
end
end
end
I want to run a specific block of code before one specific context, and it should run only once. I tried to use metadata for context block, but it calls my block of code before every example.
before do |context|
p 'test test' if context.medata[:something]
end
...
describe '#execute' do
context 'header with timelog fields', :something do
it '123' do
expect(true).to eq true
end
it '234' do
expect(true).to eq true
end
end
end
test test appears twice when I run rspec.
In rspec, writing before is shorthand for before(:each).
What you need to use, instead, is a before(:all):
describe '#execute' do
context 'header with timelog fields' do
before(:all) do
p 'test test'
end
it '123' do
expect(true).to eq true
end
it '234' do
expect(true).to eq true
end
end
end
I have a problem with rspec stubbing. I'm following this doc https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/any-instance
describe Api::V1::ActionsController, type: :controller do
let(:admin) { create :admin }
subject { response }
describe 'GET #index' do
before :each do
get :index
end
context 'admin' do
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it 'expects 200' do
expect(response.status).to eq 200
end
end
end
This test fails. And the interesting thing is that if I put these stubs to spec_helper.rb like
config.before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it works fine. Any ideas?
I guess the problem is that this piece of code:
before :each do
get :index
end
runs before the stubs:
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
before blocks are run from outside-in, and the block with the stubs is nested deeper. Therefore, by the time you stub the methods, get :index has already been executed.
Try this instead:
describe 'GET #index' do
subject do # define what `subject` will do, but don't actually run it just yet
get :index
end
context 'admin' do
before :each do
allow_any_instance_of(ApplicationController).to receive(:current_user).and_return admin
allow_any_instance_of(ApplicationController).to receive(:authenticate!).and_return true
end
it 'returns 200' do
expect(subject).to be_success
# ^^^ now it's only here that the controller action is executed
end
end
end
The block #update doesn't run. Why? How to change it to run all of them. #anything works fine.
describe UsersController, type: :controller do
login_admin
describe '#update' do
def user_update_params(roles:)
{
role_ids: roles.map(&:id),
name: 'new'
}
end
shared_examples_for 'update user' do
it 'change the user' do
expect do
put :update, id: user.id, user: user_params
end.to change { user.reload.name }
end
end
end
describe '#anything' do
it 'is ok' do
#runs ok
end
end
end
It's a shared example, not a real test. It is supposed to be included in other test groups. Like this:
describe '#whatever' do
it_behaves_like 'update user'
it 'runs shared example' do
end
end
Rails 3.2, RSpec 2.11. Controller macro isn't working, and it appears to be written correctly from all the research I've done. Here's the code
/spec/support/controller_macros.rb
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
#current_user = user
sign_in user
end
end
end
/spec/spec_helper.rb
RSpec.configure do |config|
....
config.extend ControllerMacros, :type => :controller
end
/spec/controllers/companies_controller_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
describe CompaniesController, "index companies" do
context "for authenticated users" do
login_user
...
end
end
execution results:
undefined local variable or method 'login_user' for # (NameError)
Seems to have been answered here , you need to change your extend to an include
Adding the spec type fixed it for me:
Before:
describe Api::FooController do
.
.
end
After:
describe Api::FooController, type: :controller do
.
.
end