Reputation: 2772
I can't find a way around this.
This is my test:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
let(:post_params) do
{
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
end
before do
post :create, params: post_params
end
context 'successful create' do
subject(:user) { User.find_by_email(post_params[:email_address]) }
it 'persists the user' do
expect(user).not_to be_nil
end
it 'user data is correct' do
post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
it 'returns responsde code of 201' do
expect(response.status).to eq(201)
end
end
end
end
I only want this controller to be hit once. However, I can't seem to get that to work.
I have tried setting before(:context)
and I get an error
RuntimeError:
let declaration `post_params` accessed in a `before(:context)` hook at:
`let` and `subject` declarations are not intended to be called
in a `before(:context)` hook, as they exist to define state that
is reset between each example, while `before(:context)` exists to
define state that is shared across examples in an example group.
I don't want multiple users to be persisted for such a simple test. I also dont want to be hitting the api for every example.
I want the before
block to run once. How can I do this?
Upvotes: 2
Views: 2227
Reputation: 21800
As the error message states, let
and subject
are specifically for managing per-example state. But before(:context)
/before(:all)
hooks get run outside the scope of any specific example, so they are fundamentally incompatible. If you want to use before(:context)
, you can't reference any let
definitions from the hook. You'll have to manage the post_params
state yourself without using let
. Here's a simple way to do that:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
before(:context) do
@post_params = {
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
post :create, params: @post_params
end
context 'successful create' do
subject(:user) { User.find_by_email(@post_params[:email_address]) }
it 'persists the user' do
expect(user).not_to be_nil
end
it 'user data is correct' do
@post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
it 'returns responsde code of 201' do
expect(response.status).to eq(201)
end
end
end
end
That should solve your problem; however it's not the approach I would recommend. Instead, I recommend you use the aggregate_failures
feature of RSpec 3.3+ and put all of this in a single example, like so:
require 'rails_helper'
RSpec.describe V1::UsersController do
describe '#create' do
let(:post_params) do
{
first_nm: Faker::Name.first_name,
last_nm: Faker::Name.last_name ,
password: "test123456",
password_confirmation: "test123456",
email_address: Faker::Internet.email
}
end
it 'successfully creates a user with the requested params', :aggregate_failures do
post :create, params: post_params
expect(response.status).to eq(201)
user = User.find_by_email(post_params[:email_address])
expect(user).not_to be_nil
post_params.except(:password, :password_confirmation).each do |k, v|
expect(user.send(k)).to eq(v)
end
end
end
end
aggregate_failures
gives you a failure report indicating each expectation that failed (rather than just the first one like normal), just like if you had separated it into 3 separate examples, while allowing you to actually make it a single example. This allows you to incapsulate the action you are testing in a single example, allowing you to only perform the action once like you want. In a lot of ways, this fits better with the per-example state sandboxing provided by RSpec's features like before
hooks, let
declarations and the DB-transaction rollback provided by rspec-rails, anyway. And
I like the aggregate_failures
feature so much that I tend to configure RSpec to automatically apply it to every example in spec_helper.rb
:
RSpec.configure do |c|
c.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
end
Upvotes: 2
Reputation: 1723
What you are looking for is before(:all)
, which will run once before all the cases.
There is similar after(:all)
as well.
Interestingly, the before
is basically a shorter way of saying before(:each)
(which IMO makes more sense).
Upvotes: -1