为啥“.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 日期查询和修改的方法--数据库常用操作

为啥 PHP crypt 函数使用 DES 加密算法?

为啥h2没有找到一列

我应该如何创建我的 DES 密钥?为啥 7 个字符的字符串不够用?

MySQL常用SQL