Search code examples
ruby-on-railsrubyrspecrspec-rails

How to initialize general context variables before testing any methods within a class with rspec


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.


Solution

  • 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.