为什么这个 RSpec 示例需要 "let!" 而不是 "let"?

Why does this RSpec example require "let!" instead of "let"?

我能够使用以下代码进行测试,但它似乎很奇怪,我并不完全理解它。

谁能告诉我以这种方式创建对象是否是最佳方式?

为什么我必须只使用 let! 来创建第二个 post_comment_reply 而我为什么不用其他对象?

post_comment.rb

belongs_to :post, touch: true
belongs_to :user
has_many :post_comment_replies, dependent: :destroy
has_many :users, through: :post_comment_replies

def send_post_comment_reply_creation_notification(reply)
  post_repliers = ([user] + [post.user] + users).uniq - [ reply.user ]
  post_repliers.each do |replier|
    Notification.create(recipient_id: replier.id, sender_id: reply.user_id, notifiable: self.post, action: "commented")
  end
end

post_comment_spec.rb

describe "instance methods" do
  let(:post_user) { create(:user) }
  let(:comment_user) { create(:user) }
  let(:reply_user) { create(:user) }
  let(:reply_user_2) { create(:user) }
  let(:post_reader) { create(:user) }
  let(:post) { create(:post, user: post_user) }
  let(:post_comment) { create(:post_comment, user: comment_user) }
  let(:post_comment_reply) { create(:post_comment_reply, post_comment: post_comment, user: reply_user) }
  let!(:post_comment_reply_2) { create(:post_comment_reply, post_comment: post_comment, user: reply_user_2) }


  it "send_post_comment_reply_creation_notification" do
    expect{
      post_comment.send_post_comment_reply_creation_notification(post_comment_reply)
    }.to change{Notification.count}.by(3)
  end

end

let 懒惰。如果您不引用它,则不会对其进行评估,并且在您的情况下,不会发生副作用(副作用是创建数据库条目)。

另一方面,

let! 总是被评估。

为什么需要 let!: let 是惰性的(它只在引用时运行); let! 很急切(它在测试之前运行,无论是否被引用)。您的测试需要创建 :post_comment_reply 两次; let 之所以有效,是因为测试引用了它,但是 let! 没有被引用,因此它必须是 let!,而不是 let.

它是最优的吗? 你的测试设置有效,但正如我们发现的那样,它并不像它应该的那样清晰。它还为任何向包含 let!describe 块添加更多测试的人设置了一个陷阱:无论是否需要,都会在每次测试之前创建该对象,从而减慢所有测试并可能影响结果.

相反,我会删除 let! 并写下这个(let 未显示):

describe '#send_post_comment_reply_creation_notification' do
  it "notifies each user who replies to the post_comment" do
    create(:post_comment_reply, post_comment: post_comment, user: reply_user_2)
    expect { post_comment.send_post_comment_reply_creation_notification(post_comment_reply) }.
      to change { Notification.count }.by(3)
  end
end

一般来说,更喜欢在示例(it 块)而不是在 let! 块中创建工厂对象。事实上,与 let 相比,也更喜欢在示例中创建,除非您实际上在多个示例中使用了 let 变量。 (你只展示了一个例子,但我怀疑在同一个 describe 块中确实有更多。)如果你只在一次测试中使用工厂对象,则没有理由进行 reader 搜索在你的测试文件周围定义它,或者定义一个在其他测试中可用的名称,无论它是否在那里使用。