Module: RSpec::Core::MemoizedHelpers::ClassMethods

Defined in:
lib/rspec/core/memoized_helpers.rb

Instance Method Summary collapse

Instance Method Details

#its(attribute, &block) ⇒ Object

Creates a nested example group named by the submitted attribute, and then generates an example using the submitted block.

The attribute can be a Symbol or a String. Given a String with dots, the result is as though you concatenated that String onto the subject in an expression.

When the subject is a Hash, you can refer to the Hash keys by specifying a Symbol or String in an array.

Note that this method does not modify subject in any way, so if you refer to subject in let or before blocks, you're still referring to the outer subject.

Examples:


# This ...
describe Array do
  its(:size) { should eq(0) }
end

# ... generates the same runtime structure as this:
describe Array do
  describe "size" do
    it "should eq(0)" do
      subject.size.should eq(0)
    end
  end
end

describe Person do
  subject do
    Person.new.tap do |person|
      person.phone_numbers << "555-1212"
    end
  end

  its("phone_numbers.first") { should eq("555-1212") }
end

describe "a configuration Hash" do
  subject do
    { :max_users => 3,
      'admin' => :all_permissions }
  end

  its([:max_users]) { should eq(3) }
  its(['admin']) { should eq(:all_permissions) }

  # You can still access to its regular methods this way:
  its(:keys) { should include(:max_users) }
  its(:count) { should eq(2) }
end

describe Person do
  subject { Person.new }
  before { subject.age = 25 }
  its(:age) { should eq(25) }
end


426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/rspec/core/memoized_helpers.rb', line 426

def its(attribute, &block)
  describe(attribute) do
    if Array === attribute
      let(:__its_subject) { subject[*attribute] }
    else
      let(:__its_subject) do
        attribute_chain = attribute.to_s.split('.')
        attribute_chain.inject(subject) do |inner_subject, attr|
          inner_subject.send(attr)
        end
      end
    end

    def should(matcher=nil, message=nil)
      RSpec::Expectations::PositiveExpectationHandler.handle_matcher(__its_subject, matcher, message)
    end

    def should_not(matcher=nil, message=nil)
      RSpec::Expectations::NegativeExpectationHandler.handle_matcher(__its_subject, matcher, message)
    end

    example(&block)
  end
end

#let(name, &block) ⇒ Object

Note:

let can enhance readability when used sparingly (1,2, or maybe 3 declarations) in any given example group, but that can quickly degrade with overuse. YMMV.

Note:

let uses an ||= conditional that has the potential to behave in surprising ways in examples that spawn separate threads, though we have yet to see this in practice. You've been warned.

Note:

Because let is designed to create state that is reset between each example, and before(:all) is designed to setup state that is shared across all examples in an example group, let is not intended to be used in a before(:all) hook. RSpec 2.13.1 prints a warning when you reference a let from before(:all) and we plan to have it raise an error in RSpec 3.

Generates a method whose return value is memoized after the first call. Useful for reducing duplication between examples that assign values to the same local variable.

Examples:


describe Thing do
  let(:thing) { Thing.new }

  it "does something" do
    # first invocation, executes block, memoizes and returns result
    thing.do_something

    # second invocation, returns the memoized value
    thing.should be_something
  end
end


190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rspec/core/memoized_helpers.rb', line 190

def let(name, &block)
  # We have to pass the block directly to `define_method` to
  # allow it to use method constructs like `super` and `return`.
  raise "#let or #subject called without a block" if block.nil?
  MemoizedHelpers.module_for(self).send(:define_method, name, &block)

  # Apply the memoization. The method has been defined in an ancestor
  # module so we can use `super` here to get the value.
  define_method(name) do
    __memoized.fetch(name) { |k| __memoized[k] = super(&nil) }
  end
end

#let!(name, &block) ⇒ Object

Just like let, except the block is invoked by an implicit before hook. This serves a dual purpose of setting up state and providing a memoized reference to that state.

Examples:


class Thing
  def self.count
    @count ||= 0
  end

  def self.count=(val)
    @count += val
  end

  def self.reset_count
    @count = 0
  end

  def initialize
    self.class.count += 1
  end
end

describe Thing do
  after(:each) { Thing.reset_count }

  context "using let" do
    let(:thing) { Thing.new }

    it "is not invoked implicitly" do
      Thing.count.should eq(0)
    end

    it "can be invoked explicitly" do
      thing
      Thing.count.should eq(1)
    end
  end

  context "using let!" do
    let!(:thing) { Thing.new }

    it "is invoked implicitly" do
      Thing.count.should eq(1)
    end

    it "returns memoized version on first invocation" do
      thing
      Thing.count.should eq(1)
    end
  end
end


256
257
258
259
# File 'lib/rspec/core/memoized_helpers.rb', line 256

def let!(name, &block)
  let(name, &block)
  before { __send__(name) }
end

#subject(name = nil, &block) ⇒ Object

Declares a subject for an example group which can then be the implicit receiver (through delegation) of calls to should.

Given a name, defines a method with that name which returns the subject. This lets you declare the subject once and access it implicitly in one-liners and explicitly using an intention revealing name.

Examples:


describe CheckingAccount, "with $50" do
  subject { CheckingAccount.new(Money.new(50, :USD)) }
  it { should have_a_balance_of(Money.new(50, :USD)) }
  it { should_not be_overdrawn }
end

describe CheckingAccount, "with a non-zero starting balance" do
  subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
  it { should_not be_overdrawn }
  it "has a balance equal to the starting balance" do
    .balance.should eq(Money.new(50, :USD))
  end
end

Parameters:

  • name (String, Symbol) (defaults to: nil)

    used to define an accessor with an intention revealing name

  • block

    defines the value to be returned by subject in examples

See Also:



290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/rspec/core/memoized_helpers.rb', line 290

def subject(name=nil, &block)
  if name
    let(name, &block)
    alias_method :subject, name

    self::NamedSubjectPreventSuper.send(:define_method, name) do
      raise NotImplementedError, "`super` in named subjects is not supported"
    end
  else
    let(:subject, &block)
  end
end

#subject!(name = nil, &block) ⇒ Object

Just like subject, except the block is invoked by an implicit before hook. This serves a dual purpose of setting up state and providing a memoized reference to that state.

Examples:


class Thing
  def self.count
    @count ||= 0
  end

  def self.count=(val)
    @count += val
  end

  def self.reset_count
    @count = 0
  end

  def initialize
    self.class.count += 1
  end
end

describe Thing do
  after(:each) { Thing.reset_count }

  context "using subject" do
    subject { Thing.new }

    it "is not invoked implicitly" do
      Thing.count.should eq(0)
    end

    it "can be invoked explicitly" do
      subject
      Thing.count.should eq(1)
    end
  end

  context "using subject!" do
    subject!(:thing) { Thing.new }

    it "is invoked implicitly" do
      Thing.count.should eq(1)
    end

    it "returns memoized version on first invocation" do
      subject
      Thing.count.should eq(1)
    end
  end
end


356
357
358
359
# File 'lib/rspec/core/memoized_helpers.rb', line 356

def subject!(name=nil, &block)
  subject(name, &block)
  before { subject }
end