Home > Software engineering >  Proper way to use cache rspec `let` value
Proper way to use cache rspec `let` value

Time:11-03

I got a class like that:

class Calculation
  def initialize
    heavy_calculation
  end

  def result
    'ok'
  end

  def errors
    nil
  end

  private
  
  def heavy_calculation
    p "Performing CPU-heavy calculations"
  end
end

And RSpec for checking both public methods

describe Calculation do
  let(:calculations) { Calculation.new }

  it 'result equal ok' do
    expect(calculations.result).to eq('ok')
  end

  it 'errors equal nil' do
    expect(calculations.errors).to be_nil
  end
end

Running this code we got two times "Performing CPU-heavy calculations" in the terminal, so the Calculation constructor was called twice

I was trying to refactor this code so the constructor run only once - but didn't find any solution which works flawless, without running the calculation code twice, or without leaking values to other spec files

So any advice on how to solve that correctly?

CodePudding user response:

Ok. I think that there is no proper way to solve that problem

I basically have two choices:

  1. Leak data (Heavy calculation) between examples via using before(:all) and thus not properly isolation tests cases
  2. Isolate but perform heavy calculations several times

So either of those choices comes with different tradeoffs and I must choose one of them

And seems that leave tests in current way be more correct choice

CodePudding user response:

let is intended to be lazily evaluated. The values declared by let are reset for each test so it makes sense that you see the constructor called once for each test.

If you are looking purely for a test optimization, then the solution is probably to abandon the use of let. Rspec blocks are like any other ruby code so you can declare local variables. The code below gives two passing results with only one (expensive) call to the constructor:

describe Calculation, type: :model do
  calculations = Calculation.new

  it 'result equal ok' do
    expect(calculations.result).to eq('ok')
  end

  it 'errors equal nil' do
    expect(calculations.errors).to be_nil
  end
end

If the issue is broader -- that you really only need to instantiate the class once and then track changes -- then you might want to investigate Singleton

  • Related