测试时遇到的一个问题。
背景
新建一个对象,该对象新建后会自动保存,现在需要测试其无法保存的情景,使得obj.save返回为false。
踩坑:
obj这个对象的创建中有一个属性attr设置了必填,同时该attr的值来自另一个对象a, 参考先前的代码,解决的思路是:
通过a.save(validate: false)
, 让a在attr为空时保存,然后obj创建时,会取a的attr作为自己的attr,这样obj保存时必然是false,因为atrr为空,它无法过检验,但是运行后发现,不管是a.save(validate: false)
还是a.save!(validate: false)
, 再次执行a.save
,都会回滚,返回false。虽然它们跳过了validation,但是save时,并没有成功,Google了半天,没有找出好的解决方法。后来,看到同事用stub的方式完美解决了,由衷觉得真是太赞了。
同一个问题,换个人解决,高下立判,只能说,知道和想到,隔了好几个山头啊。
记录下allow_any_instance_of的用法。
allow_any_instance_of
允许一个class的任何实例stub任何一个method的,可设置返回值,也可以不设置,文档中说可以replace allow,但是allow接受的参数是对象,而allow_any_instance_of则是类。
看一些老的版本,比如2.99,会发现allow_any_instance_of的”前身”,any_instance.stub,我以为是前身,但是在该版本的Rspec-mock的message exception部分,已经出现了allow_any_instance_of,有些奇怪,不过用起来倒是都好用的,这里一并下记录下两者的用法。
stub 单个method无参
单个method的stub,看官网的例子:
### allow_any_instance_of RSpec.describe "allow_any_instance_of" do it "returns a specified value on any instance of a class" do allow_any_instance_of(Object).to receive(:foo).and_return(:return_value) obj = Ojbect.new expect(obj.foo).to eq(:return_value) end end
用any_instance.stub 可以这样写:
### any_instance.stub RSpec.describe "any_instance.stub" do it "returns a specified value on any instance of a class" do Object.any_instance.stub(:foo).and_return(:return_value) obj = Ojbect.new expect(obj.foo).to eq(:return_value) end end
stub单个method有参
带参数的单个method,这样写:
### allow_any_instance_of RSpec.describe "allow_any_instance_of" do context "with arguments" do it "returns the stub value when arugments match" do allow_any_instance_of(Object).to receive(:foo).with(:param_one, :param_two).and_return(:return_value_one) allow_any_instance_of(Object).to receive(:foo).with(:param_another_one, :param_another_two).and_return(:return_value_another_one) obj = Ojbect.new expect(obj.foo(:param_one, :param_two)).to eq(:return_value_one) expect(obj.foo(:param_another_one, :param_another_two)).to eq(:return_value_another_one) end end end
换成any_instance.stub,可以这么来:
### any_instance.stub RSpec.describe "any_instance.stub" do context "with arguments" do it "returns the stub value when arugments match" do Object.any_instance.stub(:foo).with(:param_one, :param_two).and_return(:return_value_one) Object.any_instance.stub(:foo).with(:param_another_one, :param_another_two).and_return(:return_value_another_one) obj = Ojbect.new expect(obj.foo(:param_one, :param_two)).to eq(:return_value_one) expect(obj.foo(:param_another_one, :param_another_two)).to eq(:return_value_another_one) end end end
stub 多个method无参
多个method:
### allow_any_instance_of RSpec.describe "allow_any_instance_of" do it "stub multiple methods" do allow_any_instance_of(Object).to receive_messages(:foo => "foo", :bar => "bar") obj = Ojbect.new expect(obj.foo).to eq("foo") expect(obj.bar).to eq("bar") end end
用any_instance.stub:
### any_instance.stub RSpec.describe "any_instance.stub" do it "stub multiple methods" do Object.any_instance.stub(:foo => "foo", :bar => "bar") obj = Ojbect.new expect(obj.foo).to eq("foo") expect(obj.bar).to eq("bar") end end
any_instance.stub还有一些兄弟姐妹,比如any_instance.unstub,any_instance.stub_chain, 可以戳这里了解更多。
warning
官网上提到了慎用allow_any_instance_of,原因除了认为使用该方法 is often a design smell
,同时也是频频出bug的地方,be careful.