You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RSpec哈希参数嵌套匹配失效:如何验证哈希键为指定类实例?

RSpec验证哈希参数键为指定类实例的解决方法

问题场景

需要用RSpec验证方法调用时接收的哈希参数,其键为某个类的实例(比如示例中的IO类)。直接使用receive(:foo).with(instance_of(MyClass) => :bar)的写法无效,尝试hash_including包裹后依然失败。

复现代码

#!/usr/bin/env ruby

require "rspec"

class Account
  def initialize(logger)
    @logger = logger
  end

  def open_with_hash_arg
    pipe_r, pipe_w = IO.pipe
    @logger.account_opened(pipe_r => :read, pipe_w => :write)
  end

  def open_with_flat_args
    pipe_r, pipe_w = IO.pipe
    @logger.account_opened(pipe_r, pipe_w)
  end
end

describe Account do
  it "fails with hash arg" do
    logger = double("Logger")
    expect(logger).to receive(:account_opened).with(instance_of(IO) => :read, instance_of(IO) => :write)
    account = Account.new(logger)
    account.open_with_hash_arg
  end

  it "succeeds with flat args" do
    logger = double("Logger")
    expect(logger).to receive(:account_opened).with(instance_of(IO), instance_of(IO))
    account = Account.new(logger)
    account.open_with_flat_args
  end
end

失败输出

$ rspec ./tmp.rb
F.

Failures:

  1) Account fails with hash arg
     Failure/Error: @logger.account_opened(pipe_r => :read, pipe_w => :write)
     
       #<Double "Logger"> received :account_opened with unexpected arguments
         expected: ({an_instance_of(IO)=>:read, an_instance_of(IO)=>:write})
              got: ({#<IO:fd 5>=>:read, #<IO:fd 6>=>:write})
       Diff:
       @@ -1 +1 @@
       -[{an_instance_of(IO)=>:read, an_instance_of(IO)=>:write}]
       +[{#<IO:fd 5>=>:read, #<IO:fd 6>=>:write}]
       
     # ./tmp.rb:12:in `open_with_hash_arg'
     # ./tmp.rb:26:in `block (2 levels) in <top (required)>'

Finished in 0.01154 seconds (files took 0.04986 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./tmp.rb:22 # Account fails with hash arg

解决方案

问题根源在于:直接将instance_of(IO)作为哈希键传入时,RSpec会把它当作字面键而非匹配器。需要使用RSpec提供的a_key匹配器来指定键的匹配条件,结合hash_including即可实现需求。

修改后的测试用例代码:

it "passes with hash arg using a_key matcher" do
  logger = double("Logger")
  expect(logger).to receive(:account_opened).with(
    hash_including(
      a_key(instance_of(IO)) => :read,
      a_key(instance_of(IO)) => :write
    )
  )
  account = Account.new(logger)
  account.open_with_hash_arg
end

其他可选方案

如果需要更灵活的验证逻辑(比如确保所有键都是指定类实例,值包含预期内容),可以使用match块自定义匹配逻辑:

it "passes with hash arg using custom match block" do
  logger = double("Logger")
  expect(logger).to receive(:account_opened).with(
    match do |hash|
      # 验证所有键都是IO实例,且值包含:read和:write
      hash.keys.all? { |key| key.is_a?(IO) } &&
        hash.values.sort == [:read, :write]
    end
  )
  account = Account.new(logger)
  account.open_with_hash_arg
end

内容的提问来源于stack exchange,提问作者Nicholas Hubbard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:45:34