Rails RSpec中是否存在Mockito ArgumentCaptor的等价实现?
当然有啦!RSpec里完全有和Java ArgumentCaptor等价的功能,用来捕获传给mock对象方法的参数,之后对这些参数做各种断言。我结合你给出的Java代码示例,给你详细讲讲在Rails RSpec里怎么实现:
场景1:模拟类似Java的Repository类(接收对象作为参数的save方法)
如果你的代码结构和Java示例类似,有一个专门的Repository类负责持久化(比如TaxTreatmentRepository),它的save方法接收TaxTreatment对象作为参数,那可以直接用RSpec的ArgumentCaptor来捕获参数:
RSpec.describe ImportService do it "should import all tax treatment domain data" do # Arrange # 创建mock的Repository实例 tax_repo = instance_double(TaxTreatmentRepository) # 初始化参数捕获器 treatment_captor = RSpec::Mocks::ArgumentCaptor.new # 模拟find_by_code的调用,允许任意字符串参数,返回nil allow(tax_repo).to receive(:find_by_code).with(any_string).and_return(nil) # 模拟save方法,用捕获器捕获传入的参数 allow(tax_repo).to receive(:save).with(treatment_captor.capture).and_return(true) # 将mock的Repository注入到ImportService中 service = ImportService.new(tax_treatment_repo: tax_repo) # Act service.run_import("DOMAIN-treatment.json") # Assert # 验证find_by_code被调用了9次 expect(tax_repo).to have_received(:find_by_code).exactly(9).times # 验证save被调用了9次 expect(tax_repo).to have_received(:save).exactly(9).times # 断言捕获到的每个TaxTreatment对象的name属性符合预期 expected_names = [ "Withhold All", "Withhold", "Pay Cash", "None", "Sell All", "Sell for Taxes", "Swap", "Debit Rtl Acct", "Payroll Deduction" ] treatment_captor.all_values.each_with_index do |treatment, index| expect(treatment.name).to eq(expected_names[index]) end end end
这里treatment_captor.capture就对应Java里的argument.capture(),treatment_captor.all_values对应argument.getAllValues(),完全实现了和你Java示例一样的逻辑。
场景2:直接模拟ActiveRecord模型的实例方法(Rails常见场景)
在Rails里,我们通常直接调用模型实例的save方法(比如tax_treatment.save),而不是通过Repository类。这种情况下,我们可以通过捕获调用save的实例来断言它们的属性:
RSpec.describe ImportService do it "should import all tax treatment domain data" do # Arrange # 创建数组来存储所有调用了save的TaxTreatment实例 captured_treatments = [] # 模拟find_by_code类方法,允许任意字符串参数,返回nil allow(TaxTreatment).to receive(:find_by_code).with(any_string).and_return(nil) # 拦截所有TaxTreatment实例的save调用,把实例存入数组 allow_any_instance_of(TaxTreatment).to receive(:save) do captured_treatments << self true # 模拟save成功返回true end # Act ImportService.new.run_import("DOMAIN-treatment.json") # Assert expect(TaxTreatment).to have_received(:find_by_code).exactly(9).times # 通过数组长度验证save被调用了9次 expect(captured_treatments.size).to eq(9) # 断言每个实例的name属性符合预期 expected_names = [ "Withhold All", "Withhold", "Pay Cash", "None", "Sell All", "Sell for Taxes", "Swap", "Debit Rtl Acct", "Payroll Deduction" ] captured_treatments.each_with_index do |treatment, index| expect(treatment.name).to eq(expected_names[index]) end end end
这种方式通过allow_any_instance_of拦截所有TaxTreatment实例的save调用,把当前实例(self)存入数组,之后就可以对数组里的实例做属性断言,效果和ArgumentCaptor一致。
如果需要捕获save方法的选项参数(比如save(validate: false)里的validate参数),也可以用ArgumentCaptor来捕获:
captor = RSpec::Mocks::ArgumentCaptor.new allow_any_instance_of(TaxTreatment).to receive(:save).with(captor.capture) # 之后可以断言captor.all_values里的选项
内容的提问来源于stack exchange,提问作者Dalmer Azevedo

