模块:RSpec::Core::MemoizedHelpers::ClassMethods

包含于
ExampleGroup
定义于
lib/rspec/core/memoized_helpers.rb

概述

此模块扩展到 ExampleGroup,使这些方法可以在示例组块中调用。您可以将其视为类似于类宏。

实例方法摘要 折叠

实例方法详细信息

#let(name, &block) ⇒void

注意

let 可以在任何给定的示例组中适度使用(1、2 或可能 3 个声明)时提高可读性,但过度使用会导致可读性迅速下降。YMMV。

注意

let 可以配置为线程安全或不安全。如果它是线程安全的,访问该值的时间会更长。如果它不是线程安全的,它可能会在生成单独线程的示例中以令人惊讶的方式表现。在 RSpec.configure 上指定此选项。

注意

因为 let 旨在创建在每个示例之间重置的状态,而 before(:context) 旨在设置在示例组中的所有示例之间共享的状态,所以 let 打算在 before(:context) 钩子中使用。

生成一个方法,该方法的返回值在第一次调用后被记忆。这对于减少在示例之间分配相同局部变量的值的重复很有用。

示例


RSpec.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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/rspec/core/memoized_helpers.rb', line 306
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?
  # A list of reserved words that can't be used as a name for a memoized helper
  # Matches for both symbols and passed strings
  if [:initialize, :to_s].include?(name.to_sym)
    raise ArgumentError, "#let or #subject called with reserved name `#{name}`"
  end
  our_module = MemoizedHelpers.module_for(self)
  # If we have a module clash in our helper module
  # then we need to remove it to prevent a warning.
  #
  # Note we do not check ancestor modules (see: `instance_methods(false)`)
  # as we can override them.
  if our_module.instance_methods(false).include?(name)
    our_module.__send__(:remove_method, name)
  end
  our_module.__send__(:define_method, name, &block)
  # If we have a module clash in the example module
  # then we need to remove it to prevent a warning.
  #
  # Note we do not check ancestor modules (see: `instance_methods(false)`)
  # as we can override them.
  if instance_methods(false).include?(name)
    remove_method(name)
  end
  # 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_or_store(name) { super(RSpec.current_example, &nil) } }
  else
    define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
  end
end

#let!(name, &block) ⇒void

let 相同,只是块由一个隐式的 before 钩子调用。这具有设置状态和提供对该状态的记忆引用的双重目的。

示例


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
RSpec.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
400
401
402
403
# File 'lib/rspec/core/memoized_helpers.rb', line 400
def let!(name, &block)
  let(name, &block)
  before { __send__(name) }
end

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

注意

subject 可以配置为线程安全或不安全。如果它是线程安全的,访问该值的时间会更长。如果它不是线程安全的,它可能会在生成单独线程的示例中以令人惊讶的方式表现。在 RSpec.configure 上指定此选项。

为一个示例组声明一个 subject,然后可以使用 expectis_expected 包裹它,使其成为在一个简洁的单行示例中进行期望的目标。

给定一个 name,定义一个具有该名称的方法,该方法返回 subject。这允许您声明一次主题,并在单行代码中隐式地访问它,以及使用揭示意图的名称显式地访问它。

当给定一个 name 时,在块中调用 super 不受支持。

示例


RSpec.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
RSpec.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
    .balance.should eq(Money.new(50, :USD))
  end
end

参数

  • name (String, Symbol) (默认值为: nil)

    用于定义一个具有揭示意图的名称的访问器

  • block

    定义示例中 subject 返回的值

参见

444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/rspec/core/memoized_helpers.rb', line 444
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) ⇒void

subject 相同,只是块由一个隐式的 before 钩子调用。这具有设置状态和提供对该状态的记忆引用的双重目的。

示例


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
RSpec.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
510
511
512
513
# File 'lib/rspec/core/memoized_helpers.rb', line 510
def subject!(name=nil, &block)
  subject(name, &block)
  before { subject }
end