为啥“.order(”created_at DESC“)”在Rspec中搞砸了“允许”?
Posted
技术标签:
【中文标题】为啥“.order(”created_at DESC“)”在Rspec中搞砸了“允许”?【英文标题】:Why does ".order("created_at DESC")" mess up "allow" in Rspec?为什么“.order(”created_at DESC“)”在Rspec中搞砸了“允许”? 【发布时间】:2021-12-17 09:47:28 【问题描述】:在 Rspec 中遇到问题。假设我有这个:
class Book; has_many: :pages; end
class Page; belongs_to: :book; end
describe Pages
let(:book) create(:book)
let(:page) create(:page, book: book)
before do
allow(page).to receive(:last_page?).and_return(last_page)
book.pages << page
end
context "weird behavior" do
let(:last_page) "Orange"
it do
# these two pass as expected
expect(book.pages.first).to eq page # passes, as expected
expect(book.pages.first.last_page?).to eq "Orange" # passes, as expected
# this is where weird things happen
expect(book.pages.order("created_at DESC").first).to eq page # passes, as expected
expect(book.pages.order("created_at DESC").first.last_page?).to eq "Orange" # will fail and return the actual method call
end
end
end
为什么 ".order("created_at DESC")" 会弄乱 "allow" 语句,即使实际对象仍然相等?
【问题讨论】:
如果这是整个规范文件expect(book.pages.first.last_page?).to eq "Orange"
没有通过,last_page?
仍然会从未被存根的 book.pages.first
调用。
也许澄清你的意图会引导你找到更好的方法。
.last_page?
应该按照 Ruby 约定返回一个布尔值。它返回一个字符串的事实本身就是一个 WTF 时刻。
@SebastianPalma 你是对的,那条线实际上没有通过。但是,当我在控制台中调试时,如果我运行book.pages
然后book.pages.first.last_page?
,我会得到“橙色”。如果我运行“book.pages.reload.first.last_page?”,那么它会返回到方法调用。知道为什么吗?
【参考方案1】:
我可以通过这个。
describe Pages
let(:book) create(:book)
let(:page) create(:page, book: book)
before do
# here you create the expectation on the page object
allow(page).to receive(:last_page?).and_return(last_page)
# and append the page to the pages collection
# this collection / scope could be considered to be already loaded from the DB if you like
book.pages << page
end
context "weird behavior" do
let(:last_page) "Orange"
it do
# these two pass as expected
# and here you are simply extracting the page object back from what is effectively a loaded pages scope.
expect(book.pages.first).to eq page # passes, as expected
expect(book.pages.first.last_page?).to eq "Orange" # passes, as expected
# it's equivalent to
expect(page).to eq page # passes, as expected
expect(page.last_page?).to eq "Orange" # passes, as expected
# I would expect this to fail since the first page would be different to the object you created the expectation on
expect(book.pages.reload.first).to eq page # passes, as expected
expect(book.pages.reload.first.last_page?).to eq "Orange" # passes, as expected
# this is where weird things happen
# not so weird.
# book.pages is one scope object
# book.pages.limit(1) would be another
# book.pages.order('id') would be another
# book.pages.order('created_at DESC') would fetch from the database and that object would not have your expectation on it
# expections can only be made on an object not on every instance of an object with that id.
expect(book.pages.order("created_at DESC").first).to eq page # passes, as expected
expect(book.pages.order("created_at DESC").first.last_page?).to eq "Orange" # will fail and return the actual method call
end
end
end
【讨论】:
感谢您彻底解释此@RobL。我想我现在明白其中的区别了。我看到的另一件我不明白的事情是:当我运行book.pages.first.last_page?
时,它实际上返回了方法调用(不是“橙色”,我在原始帖子中错误地说。)但是,如果我第一次运行book.pages
,然后book.pages.first.last_page?
,然后它确实返回“橙色”。如果我再做book.pages.reload.first.last_page?
,我会再次得到方法调用。为什么会这样?【参考方案2】:
发生了什么:
book.pages.first.last_page?
first
返回之前块中设置的对象:
book.pages << page
所以当你运行 allow(page)
和 book.pages.first
他们指向同一个对象时,试试
page.object_id == book.pages.first.object_id # true
但是当你打电话时
book.pages.order("created_at DESC").first
Rails 正在从数据库中获取所有相关页面并构建全新的对象,所以
page.object_id == book.pages.order("created_at DESC").first.object_id # false
所以模拟对象page
是与book.pages.order("created_at DESC").first.object_id
不同的对象,后者只是调用在Page 模型上实现的原始方法last_page?
。
换句话说:你的 mock 是针对内存中的这个特定对象,调用 order
会为 DB 获取记录并在内存中创建不同的对象,而你没有在这些对象上设置 mock。
有很多方法可以修复它(更好或更糟,取决于您正在设计的系统的其他一些特性),这值得一个完整的单独堆栈溢出问题恕我直言(随意发布!)。但我希望你清楚为什么你的一个期望通过而另一个失败。
【讨论】:
以上是关于为啥“.order(”created_at DESC“)”在Rspec中搞砸了“允许”?的主要内容,如果未能解决你的问题,请参考以下文章
SQLSTATE [23000]:完整性约束违规:1052 列“created_at”在 order 子句中不明确 Laravel 5.5
Ruby on Rails 日期查询和修改的方法--数据库常用操作