0
votes

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.

2

2 Answers

1
votes

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.

0
votes

Try this way

RSpec.describe MyService do
  describe "#create_from_order" 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)}

    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
end

Also let method is being run if only the objects is called. If you want it to be run no matter what, you should use let! method.

Hope it helps