How can I test memoization with RSpec?

3k views Asked by At

I have the next code in one of my classes:

class Settings < ActiveRecord::Base
  def self.current
    @settings ||= Settings.where({ environment: Rails.env }).first_or_create!
  end
  # Other methods
end

Basic behaviour:

  1. It creates a new record on first call.
  2. It returns the same result with subsequent calls.
  3. It resets the ivar after each update, returning the first (and unique) record associated to current environment in subsequent calls.

For this method I have the next test:

describe Settings do
  describe ".current" do
    it "gets all settings for current environment" do
      expect(Settings.current).to eq(Settings.where({ environment: 'test' }).first)
    end
  end
end

I don't feel comfortable with that because I'm actually not testing memoization, so I have been following the approach on this question, and I have tried something like that:

describe ".current" do
  it "gets all settings for current environment" do
    expect(Settings).to receive(:where).with({ environment: 'test' }).once
    2.times { Settings.current }
  end
end

But this test returns the following error:

NoMethodError:
  undefined method `first_or_create!' for nil:NilClass

So my question is, how can I test memoization on this method with RSpec?

UPDATE:

Finally, my approach is as follows:

describe Settings do
  describe ".current" do
    it "gets all settings for current environment" do
      expect(described_class.current).to eq(described_class.where(environment: 'test').first)
    end
    it "memoizes the settings for current environment in subsequent calls" do
      expect(described_class).to receive(:where).once.with(environment: 'test').and_call_original
      2.times { described_class.current }
    end
  end
end
2

There are 2 answers

2
Ash Wilson On BEST ANSWER

Add an .and_call_original to your message expectation:

expect(Settings).to receive(:where).with({ environment: 'test' }).once.and_call_original
0
Gjaldon On

The code below calls Settings.current twice and checks if they are both equal to each other to check if the value has been memoized. We also stub out all other methods that we aren't testing.

describe ".current" do
  it "gets all settings for current environment" do
    relation = double("relation")
    expect(relation).to receive(:first_or_create!).once
    expect(Settings).to receive(:where).with({ environment: 'test' }).once.and_return(relation)
    settings = Settings.current
    expect(Settings.current).to eq settings
  end
end