【问题标题】:Why does this RSpec example require "let!" instead of "let"?为什么这个 RSpec 示例需要“让!”而不是“让”?
【发布时间】:2016-04-18 12:14:43
【问题描述】:

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

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

为什么我必须只使用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!it 块运行之前创建 post_comment_reply_2。它如何影响预期?如果删除post_comment_reply_2,测试如何失败?
  • 计数变化 2 而不是预期的 3。
  • 在测试之前创建post_comment_reply_2会影响其结果是没有意义的。 change 将在 it 块的开头查看 Notification.count,在创建 post_comment_reply_2 之后。 post_comment_reply_2 对改变有何贡献?
  • 该方法将通知发送给所有回复给定post_comment 的用户。由于两个回复都属于同一个post_comment,我认为这是预期的结果。还是我弄错了?
  • 我只是不清楚对象关系。我现在明白了,所以我可以正确地写出我心中的答案。

标签: ruby-on-rails ruby rspec factory-bot


【解决方案1】:

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

另一方面,let! 总是被评估。

【讨论】:

  • Sergio,你能用这段代码解释一下吗?我的意思是为什么必须先评估第二个回复,而其他的则不需要?
  • @SzilardMagyar:你在测试中使用post_comment_reply。它在您正在测试的逻辑之前进行评估。它会导致对大多数其他 let 的评估,因为它们是它的依赖项。如果你也用了post_comment_reply_2,你就不用let!了,let也可以。
  • 啊,所以这部分expect{ post_comment.send_post_comment_reply_creation_notification(post_comment_reply) } 中调用的内容可以与let 一起使用,但是使用change{Notification.count}.by(3) 获得正确结果所需的其余部分必须使用let! 进行评估?
  • 这是一种方法,是的。或者,您可以使用before 阻止您的强制性副作用。
  • 你觉得这个解决方案足够好,还是我的方法在创建必要的对象时搞砸了?
【解决方案2】:

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

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

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

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 块中确实有更多示例。)如果您仅在一项测试中使用工厂对象,则没有理由让读者在您的测试文件中寻找位置它已定义,或者定义在其他测试中可用的名称,无论它是否在那里使用。

【讨论】:

  • 谢谢戴夫!现在说得通了!
猜你喜欢
  • 2017-12-21
  • 2017-08-14
  • 1970-01-01
  • 2020-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-15
相关资源
最近更新 更多