This module is extended onto {ExampleGroup}, making the methods available to be called from within example group blocks. You can think of them as being analagous to class macros.
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.
@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(:context)` 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(:context)` hook.
@example
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
# File lib/rspec/core/memoized_helpers.rb, line 231 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. if block.arity == 1 define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(RSpec.current_example, &nil) } } else define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(&nil) } } end end
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.
@example
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(:example) { 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
# File lib/rspec/core/memoized_helpers.rb, line 299 def let!(name, &block) let(name, &block) before { __send__(name) } end
Declares a `subject` for an example group which can then be wrapped with `expect` using `is_expected` to make it the target of an expectation in a concise, one-line example.
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.
@param name [String,Symbol] used to define an accessor with an
intention revealing name
@param block defines the value to be returned by `subject` in examples
@example
describe CheckingAccount, "with $50" do subject { CheckingAccount.new(Money.new(50, :USD)) } it { is_expected.to have_a_balance_of(Money.new(50, :USD)) } it { is_expected.not_to be_overdrawn } end describe CheckingAccount, "with a non-zero starting balance" do subject(:account) { CheckingAccount.new(Money.new(50, :USD)) } it { is_expected.not_to be_overdrawn } it "has a balance equal to the starting balance" do account.balance.should eq(Money.new(50, :USD)) end end
@see RSpec::Core::MemoizedHelpers#should @see RSpec::Core::MemoizedHelpers#should_not @see RSpec::Core::MemoizedHelpers#is_expected
# File lib/rspec/core/memoized_helpers.rb, line 336 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
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.
@example
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(:example) { 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
# File lib/rspec/core/memoized_helpers.rb, line 402 def subject!(name=nil, &block) subject(name, &block) before { subject } end