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
相关产品推荐
相关产品推荐

