【问题标题】: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?”,那么它会返回到方法调用。知道为什么吗?

标签: ruby-on-rails rspec


【解决方案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。

有很多方法可以修复它(更好或更糟,取决于您正在设计的系统的其他一些特性),这值得一个完整的单独堆栈溢出问题恕我直言(随意发布!)。但我希望你清楚为什么你的一个期望通过而另一个失败。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 2014-02-13
    • 2018-11-05
    • 2012-02-29
    • 1970-01-01
    相关资源
    最近更新 更多