【问题标题】:RSpec thinks that block does not receive "call" message?RSpec 认为该块没有收到“呼叫”消息?
【发布时间】:2013-11-19 01:37:22
【问题描述】:

我想使用 RSpec 来确保我的可枚举类与 Ruby 的访问者模式兼容:

# foo.rb
class Foo
  def initialize(enum)
    @enum = enum
  end
  include Enumerable
  def each(&block)
    @enum.each(&block)
  end
end

这是我的 rspec 文件:

# spec/foo_spec.rb
require 'rspec' 
require './foo.rb' 

describe Foo do 
  let(:items) { [1, 2, 3] } 
  describe '#each' do 
    it 'calls the given block each time' do 
      block = proc { |x| x }
      block.should_receive(:call).exactly(items.size).times 
      Foo.new(items).each(&block) 
    end 
  end 
end

但令人惊讶的是,我的示例在运行时失败了(使用 rspec v2.14.5):

# $ bundle exec rspec

Failures:

  1) Foo#each calls the given block each time
     Failure/Error: block.should_receive(:call).exactly(items.size).times
       (#<Proc:0x007fbabbdf3f90@/private/tmp/rspec-mystery/spec/foo_spec.rb:8>).call(any args)
           expected: 3 times with any arguments
           received: 0 times with any arguments
     # ./spec/foo_spec.rb:12:in `block (3 levels) in <top (required)>'

Finished in 0.00082 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/foo_spec.rb:11 # Foo#each calls the given block each time

更令人惊讶的是,当通过 ruby​​/irb 使用时,该类本身的行为完全符合我的预期:

# $ irb -r ./foo.rb

1.9.3-p125 :002 > f = Foo.new [1, 2, 3]
 => #<Foo:0x007ffda4059f70 @enum=[1, 2, 3]> 
1.9.3-p125 :003 > f.each
 => #<Enumerator: [1, 2, 3]:each> 
1.9.3-p125 :004 > block = proc { |x| puts "OK: #{x}" }
 => #<Proc:0x007ffda483fcd0@(irb):4> 
1.9.3-p125 :005 > f.each &block
OK: 1
OK: 2
OK: 3
 => [1, 2, 3] 

为什么 RSpec 没有注意到“块”实际上确实收到了 3 次“调用”消息?

【问题讨论】:

    标签: ruby rspec


    【解决方案1】:

    为什么 RSpec 没有注意到“块”实际上确实收到了 3 次“调用”消息?

    因为,AFAICT,在 MRI 上,它没有。

    #each 不由Enumerable 提供,仅由实现它的类提供,并且在您的测试中,您使用的是数组。

    这是来自Array#each的源代码(C):

    VALUE rb_ary_each(VALUE array)
    {
        long i;
        volatile VALUE ary = array;
    
        RETURN_SIZED_ENUMERATOR(ary, 0, 0, rb_ary_length);
        for (i=0; i<RARRAY_LEN(ary); i++) {
            rb_yield(RARRAY_PTR(ary)[i]);
        }
        return ary;
    }
    

    从这里,它看起来像 Array#each yields 到块,而不是 call 明确地。

    更新

    您的代码和测试在 Rubinius 和 JRuby 上也失败了,所以看起来他们的标准库在这里也没有使用 call。正如@mechanicalfish 指出的那样,您实际上只需要测试迭代器遍历集合的次数是否正确。

    【讨论】:

    • 那么你知道在 RSpec 中是否有任何方法可以在一个块上设置期望值,它将被 yielded 到,就像你可以在 Proc 上设置期望值一样它将是called(即receive(:call)?
    • 好问题。我不知道。在测试中用 :yield 替换 :call 并没有通过,所以也许有一种不同的机制来测试可以降到 C 级别的东西?
    • 嗯,所以我想知道是否可以确定是否调用了 proc?理想情况下,无论调用是通过“yield”还是“call”方法。
    • @maerics:这似乎是一种方法:stackoverflow.com/a/2288239/10569
    【解决方案2】:

    根据 Matz 在https://www.ruby-forum.com/topic/71221 的评论,在 MRI 中让出之前,块不会变成 Procs,因此可以理解,它们没有在让出过程中收到 :call。此外,我不相信有任何方法可以对块设置期望,因为在 Ruby 语言中没有办法将块作为对象引用。

    但是,您可以对 Procs 设置期望,即他们将收到 :call 消息,并且事情会按照您的预期进行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-09
      • 1970-01-01
      • 2019-02-08
      • 1970-01-01
      • 2011-09-07
      • 2017-10-03
      相关资源
      最近更新 更多