Rspec 让变量在块之前在示例之间不改变

ssb*_*ssb 2 ruby rspec ruby-on-rails

在 rspec 3.2 中,我有一些基于以下伪代码的内容:

context 'my test context' do
  before do
    method_that_uses(error_message)
  end

  subject { post :my_action, params: a_bunch_of_params }

  let(:error_message) { 'error' }

  it { is_expected.to raise_error(MyException) }

  let(:error_message) { 'different error' }

  it { is_expected.to redirect_to(a_path) }

  let(:error_message) { 'third error' }

  it { is_expected.to redirect_to(another_path) }
end
Run Code Online (Sandbox Code Playgroud)

error_message每个示例都以set to运行third error。我也通过从 before 钩子运行 pry 来确认这一点。我怎样才能获得所需的行为?

Sim*_*ime 5

发生这种情况是因为在内部let使用了Define_method ,如let 的源代码中所示。您可以创建一个快速示例

class A
  def create_method(name, &block)
    self.class.send(:define_method, name, &block)
  end
end

a = A.new
a.create_method(:foo) { puts "bar" }
a.create_method(:foo) { puts "baz" }
a.foo
Run Code Online (Sandbox Code Playgroud)

并运行它,您会看到它define_method用新方法覆盖了以前的方法。因此,在您的示例中,您正在创建一个方法,然后在有机会调用它之​​前重写它的定义两次。

您希望error_message在其自己的上下文中运行每个程序,如下所示:

def method_that_uses(e)
  puts "running with: #{e}"
end

context 'my test context' do
  before do
    method_that_uses(error_message)
  end

  context 'error' do
    let(:error_message) { 'error' }

    it { puts "run one" }
  end

  context 'second error' do
    let(:error_message) { 'different error' }

    it { puts "run two" }
  end

  context' third error' do
    let(:error_message) { 'third error' }

    it { puts "run three" }
  end
end
Run Code Online (Sandbox Code Playgroud)

当运行时,输出

running with: error
run one
.running with: different error
run two
.running with: third error
run three
.
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为describecontext块创建了一个新的ExampleGroup)和ExampleGroup状态

示例组主体(例如describecontext块)在ExampleGroup 的新子类的上下文中进行评估。

因此,let(:error_message)现在在不同的子类上定义这些方法。