我正在成功测试 ActiveRecord 模型的某些属性是否已更新。我还想测试是否只有这些属性发生了变化。我希望我可以连接到模型的
.changes
或 .previous_changes
方法来验证我期望更改的属性是唯一要更改的属性。
更新
寻找与以下内容等效的东西(这不起作用):
it "only changes specific properties" do
model.do_change
expect(model.changed - ["name", "age", "address"]).to eq([])
end
尝试这样的事情
expect { model.method_that_changes_attributes }
.to change(model, :attribute_one).from(nil).to(1)
.and change(model, :attribute_two)
如果更改的不是属性,而是关系,您可能需要重新加载模型:
# Assuming that model has_one :foo
expect { model.method_that_changes_relation }
.to change { model.reload.foo.id }.from(1).to(5)
编辑:
经过OP评论的一些澄清后:
那么你可以这样做
# Assuming, that :foo and :bar can be changed, and rest can not
(described_class.attribute_names - %w[foo bar]).each |attribute|
specify "does not change #{attribute}" do
expect { model.method_that_changes_attributes }
.not_to change(model, attribute.to_sym)
end
end
end
这基本上就是您所需要的。
此解决方案有一个问题:它将为每个属性调用
method_that_changes_attributes
,这可能效率低下。如果是这种情况 - 您可能想要创建自己的匹配器来接受一系列方法。从这里开始
也许这可以帮助:
model.do_change
expect(model.saved_changes.keys).to contain_exactly 'name', 'age', 'address'
这应该也适用于
.previous_changes
。
如果未保存更改,则
.changed
应该可以工作。
归根结底,这实际上取决于事情如何发生
do_change
截至撰写本文时,我们可以做到,
let!(:model) { create(:user, name: 'old_name', age: 29, address: 'old_address')
let(:name) { 'New name' }
let(:age) { 30 }
let(:address { 'new address' }
it { expect { model.do_change; model.reload }.to_not change {
model.attributes.slice!("name", "age", "address", "updated_at") } }
it { expect { model.do_change; model.reload }\
.to change { model.name }.from('old_name').to(name)\
.and change { model.age }.by(1)\
.and change { model.address }.from('old_address').to(address) } }
这就是我解决@Greg 的答案中提到的低效率问题的方法,但根据要检查的属性数量,两种方法都可以更快,
但请确保为
model.create
分配不同的初始值,并注意 updated_at
,有时会被视为未更改(忽略部分毫秒)。
附注如果有人知道如何将这两者结合在一个测试中,请发表评论。