I am new to rspec. I am writting a test for a service class and I would like a bunch of instances to be initialized before I run in any of the describe block. I do something like :
RSpec.describe MyService do
before :each do
let(:product){create(:product)}
let(:article_student){ create(:article, targets: [:student], vat_type: vat_type, product: product)} #target student
let(:article_teacher){ create(:article, targets: [:teacher], vat_type: vat_type, product: product) }#target teacher
# creer des offres
let(:offer){create(:offer, target: :student, license_length: :m12)}
let(:offer_special_cyclic){create(:offer_special, cyclic_amount: true, free_article_id: article_teacher.id, minimum_amount: 10, license_length: :m12)} # free article @article_teacher
let(:offer_special_non_cyclic){create(:offer_special, cyclic_amount: false, free_article_id: article_teacher.id, minimum_amount: 10, license_length: :m12)} # free article @article_teacher
let(:order){ create(:order, establishment_account: establis
hment_account)}
end
then I will have several describe block (testing the different methods in MyService and I would like in each of them to modify the previously created variables in my before :each block. For instance :
describe "#create_from_order" do
subject{ licenses }
context "first time worker runs" do
order.already_went_through_license_worker = false
order.save!
LicenseService.new.create_from_order(@order.id)
let(:licenses){ License.where(order_id: @order.id)}
specify { subject.count.to eq 34 }
specify { subject.pluck(:is_from_offer_special).count(true).to eq 4}
specify { subject.pluck(:is_from_offer_special).count(false).to eq 30 }
end
end
However when I try to run my test I get that the order
inside my context
block is not defined...
undefined local variable or method `order' for #<Class:0x007fc689ddfdb8>
Im realising my before :each code is never entered. What is the good way to achieve this, that is initialize a bunch of instance variable general context before testing any methods within the class.
First, move your let
statements out of the before
block. let
is lazily evaluated, which means the blocks you define (let(:foo) { block }
) is only executed when foo
is called. But by wrapping the statements in a before
block, you are not actually calling them, so nothing will happen.
To execute the statements before each test, use let!
. This is not lazily evaluated, and gets executed as soon as you define the statement, which is what you want in this case.
RSpec.describe MyService do
let!(:product){create(:product)}
let!(:article_student){ create(:article, targets: [:student], vat_type: vat_type, product: product)} #target student
let!(:article_teacher){ create(:article, targets: [:teacher], vat_type: vat_type, product: product) } #target teacher
# creer des offres
let!(:offer){create(:offer, target: :student, license_length: :m12)}
let!(:offer_special_cyclic){create(:offer_special, cyclic_amount: true, free_article_id: article_teacher.id, minimum_amount: 10, license_length: :m12)} # free article @article_teacher
let!(:offer_special_non_cyclic){create(:offer_special, cyclic_amount: false, free_article_id: article_teacher.id, minimum_amount: 10, license_length: :m12)} # free article @article_teacher
let!(:order){ create(:order, establishment_account: establishment_account)}
end
Now, to modify the objects, use a before
block within the context. And make sure you are not mixing order
and @order
.
context "first time worker runs" do
before do
order.already_went_through_license_worker = false
order.save!
LicenseService.new.create_from_order(order.id)
end
let(:licenses){ License.where(order_id: order.id)}
specify { subject.count.to eq 34 }
specify { subject.pluck(:is_from_offer_special).count(true).to eq 4}
specify { subject.pluck(:is_from_offer_special).count(false).to eq 30 }
end
That should solve your issue.