【问题标题】:Trouble differentiating Rspec's 'let' vs 'let!'难以区分 Rspec 的“让”与“让!”
【发布时间】:2013-06-28 18:24:06
【问题描述】:

我已经阅读了rspec docs 并搜索了许多其他地方,但我很难理解 Rspec 的 letlet! 之间的区别

我读到let 直到需要它才被初始化,并且它的值仅在每个示例中被缓存。我还读到let! 强制变量立即存在,并强制对每个示例进行调用。我想因为我是新手,所以我很难看到这与以下示例有何关系。为什么:m1需要设置let!才能断言页面上存在m1.content,而:user可以设置let断言页面包含text: user.name

  subject { page }

  describe "profile page" do
    let(:user) { FactoryGirl.create(:user) }
    let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
    let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }

    before { visit user_path(user) }

    it { should have_selector('h1',    text: user.name) }
    it { should have_selector('title', text: user.name) }

    describe "microposts" do
      it { should have_content(m1.content) }
      it { should have_content(m2.content) }
      it { should have_content(user.microposts.count) }
    end
  end

  describe "after saving the user" do
    before { click_button submit }
    let(:user) { User.find_by_email('user@example.com') }

    it { should have_selector('title', text: user.name) }
    it { should have_success_message('Welcome') } 
    it { should have_link('Sign out') }
  end

【问题讨论】:

    标签: rspec let


    【解决方案1】:

    因为 before 块正在调用 visit user_path(user),所以用户值在那里被初始化并且 RSpec 将访问该页面。如果:m1 :m2 没有使用let! 那么访问将不会产生任何内容

    it { should have_content(m1.content) }
    it { should have_content(m2.content) }
    

    失败,因为它希望在用户访问页面之前创建微博。 let! 允许在调用 before 块之前创建微博,并且当测试访问页面时,微博应该已经创建。

    编写相同测试并使其通过的另一种方法是执行以下操作:

    describe "profile page" do
      let(:user) { FactoryGirl.create(:user) }
      let(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
      let(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }
    
      before do
        m1
        m2
        visit user_path(user)
      end
    

    visit user_path(user) 之前调用变量m1m2 会导致它们在页面被访问之前被初始化并导致测试通过。

    更新 这个小例子会更有意义:

    在这个例子中,我们调用 get_all_posts,它只返回一个帖子数组。请注意,我们在断言之前和 it 块执行之前调用该方法。因为 post 在断言执行之前不会被调用。

    def get_all_posts
      Post.all
    end
    
    let(:post) { create(:post) }
    
    before { @response = get_all_posts }
    
    it 'gets all posts' do 
      @response.should include(post)
    end
    

    通过使用let!,只要 RSpec 看到该方法(在before 块之前),就会创建帖子,并且帖子会在Post 列表中返回

    同样,另一种方法是在调用方法之前在 before 块中调用变量名

    before do
      post
      @response = get_all_posts
    end
    

    因为这将确保在调用方法本身之前调用 let(:post) 块创建 Post 以便它在 Post.all 调用中返回

    【讨论】:

    • 这很有意义。那么,使用 let 向对象 (m1) 发送消息(内容)还不够,因为 m1 还没有被初始化?
    • 这不是测试失败的原因。测试失败的原因是因为您在创建微博之前已经“访问过该页面”,因此该页面没有内容。如果您仍有疑问,我可以更新我的答案以使其更清楚一点?
    • 好吧,在再次查看我的问题(“保存用户后”)之后,第二个测试如何能够检查“user.name”?
    • 在某种程度上,事实是 let 执行你传递给它的块并将结果分配给 (:m1)。重要的是块内有什么。哪个正在创建微帖子记录,并且控制器/视图的某处正在返回该记录,导致内容与 m1 中的内容匹配,这是FactoryGirl.create(:microposts) 的结果
    • 我理解你的回答,但我仍然对测试(“保存用户后”)如何能够调用“user.name”(如果它只使用“let”)感到困惑?我认为在您在某处调用“用户”之前,let 块不会执行?当 user.name 与 let 在同一个块中时,情况不是这样吗???任何帮助将不胜感激。我显然错过了什么?
    【解决方案2】:

    区分的关键在于 rspec 如何执行这些步骤。

    再看代码:

    let(:user) { FactoryGirl.create(:user) }
    let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
    let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }
    
    before { visit user_path(user) }
    

    如果我们使用 let 而不是 let!,则此时不会创建 m1 和 m2。然后rspec进行访问,页面加载完毕,但是页面上显然没有m1和m2。

    所以现在如果我们调用 m1 和 m2,它们将在内存中创建。但是已经太晚了,因为除非我们故意这样做,否则页面不会再次加载。因此页面上的任何 UI 测试都会导致失败。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 1970-01-01
      • 2011-08-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-10
      • 1970-01-01
      相关资源
      最近更新 更多