8

我正在尝试执行Michael Hartl 的Ruby on Rails 教程中第 8.5 章的练习 2。练习如下:

按照第 8.3.3 节中的示例,浏览用户和身份验证请求规范(即当前位于 spec/requests 目录中的文件)并在 spec/support/utilities.rb 中定义实用程序函数以将测试与实现分离。额外的功劳:将支持代码组织到单独的文件和模块中,并通过将模块正确地包含在规范帮助文件中来使一切正常工作。

示例 8.3.3:实用程序.rb

include ApplicationHelper

def valid_signin(user)
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
end

RSpec::Matchers.define :have_error_message do |message|
  match do |page|
    page.should have_selector('div.alert.alert-error', text: message)
  end
end

定义的valid_signin(user)函数在以下块中使用authentication_pages_spec.rb并且工作正常。

describe "with valid information" do
    let(:user){FactoryGirl.create(:user)}
    before { valid_signin(user) }

    it { should have_selector('title', text: user.name) }
    it { should have_link('Profile', href: user_path(user)) }
    it { should have_link('Sign out', href: signout_path) }
    it { should_not have_link('Sign in', href: signin_path) }

    describe "followed by signout" do
        before { click_link "Sign out" }
        it { should have_link('Sign in') }
    end
end

因此,通过这个示例,我开始创建自己的命名valid_signup(user)

def valid_signup(user)
    fill_in "Name",         with: user.name
    fill_in "Email",        with: user.email
    fill_in "Password",     with: user.password
    fill_in "Confirmation",         with: user.password_confirmation
end

我正在user_pages_spec.rb像这样使用这个块:

describe "with valid information" do
  let(:user){FactoryGirl.create(:user)}
  before { valid_signup(user) }

  it "should create a user" do
    expect { click_button submit }.to change(User, :count).by(1)
  end

  describe "after saving the user" do
    before { click_button submit }
    let(:user) { User.find_by_email(user.email) }

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

它不起作用。Spork/Guard 报告以下错误:

Failures:

  1) UserPages signup with valid information should create a user
     Failure/Error: expect { click_button submit }.to change(User, :count).by(1)
       count should have been changed by 1, but was changed by 0
     # ./spec/requests/user_pages_spec.rb:46:in `block (4 levels) in '

  2) UserPages signup with valid information after saving the user 
     Failure/Error: before { valid_signup(user) }
     NoMethodError:
       undefined method `name' for nil:NilClass
     # ./spec/support/utilities.rb:10:in `valid_signup'
     # ./spec/requests/user_pages_spec.rb:43:in `block (4 levels) in '

  3) UserPages signup with valid information after saving the user 
     Failure/Error: before { valid_signup(user) }
     NoMethodError:
       undefined method `name' for nil:NilClass
     # ./spec/support/utilities.rb:10:in `valid_signup'
     # ./spec/requests/user_pages_spec.rb:43:in `block (4 levels) in '

  4) UserPages signup with valid information after saving the user 
     Failure/Error: before { valid_signup(user) }
     NoMethodError:
       undefined method `name' for nil:NilClass
     # ./spec/support/utilities.rb:10:in `valid_signup'
     # ./spec/requests/user_pages_spec.rb:43:in `block (4 levels) in '

这些错误似乎表明我的valid_signup(user)函数中的 user.nameutilities.rb未定义,但我看不出任何原因。我已经多次重新启动 Guard,并rake db:test:prepare确保测试数据库(使用 postgresql)是有序的。

这是我factories.rb的完整性:

FactoryGirl.define do
    factory :user do
        name    "Example User"
        email   "user@example.com"
        password    "foobar"
        password_confirmation   "foobar"
    end
end

在我继续尝试解耦更多的测试套件之前,我非常想解决这个错误,更重要的是,了解它的原因。

编辑

我已经尝试了您的提示,并按user_pages_spec.rb如下方式编辑了函数:

describe "with valid information" do
      before { valid_signup(user) }

      it "should create a user" do
        expect { click_button submit }.to change(User, :count).by(1)
      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_selector('div.alert.alert-success', text: 'Welcome') }
        it { should have_link('Sign out') }
      end
    end

由于我let(:user){FactoryGirl.create(:user)}从函数中删除,我猜想函数中不再创建用户,所以我需要定义valid_signup(user)变量userforvalid_signup不再由 FactoryGirl 填充:

def valid_signup(user)
    fill_in "Name",     with: "Example User"
    fill_in "Email",    with: "user@example.com"
    fill_in "Password", with: "foobar"
    fill_in "Confirmation", with: "foobar"
end

这不起作用,并给了我以下错误:

Failures:

1) UserPages signup with valid information should create a user Failure/Error: before { valid_signup(user) } NameError: undefined local variable or method user' for #<RSpec::Core::ExampleGroup::Nested_5::Nested_3::Nested_2:0x007fdafc5088c0> # ./spec/requests/user_pages_spec.rb:42:inblock (4 levels) in '

2) UserPages signup with valid information after saving the user Failure/Error: it { should have_selector('title', text: user.name) } NoMethodError: undefined method name' for nil:NilClass # ./spec/requests/user_pages_spec.rb:52:inblock (5 levels) in '

我还尝试valid_signup(user)以以前的方式运行测试(使用user.name, user.email, user.password, user.password_confirmation,这也不起作用,出现错误:

Failures:

  1) UserPages signup with valid information should create a user
     Failure/Error: before { valid_signup(user) }
     NameError:
       undefined local variable or method `user' for #
     # ./spec/requests/user_pages_spec.rb:42:in `block (4 levels) in '

  2) UserPages signup with valid information after saving the user 
     Failure/Error: it { should have_selector('title', text: user.name) }
     NoMethodError:
       undefined method `name' for nil:NilClass
     # ./spec/requests/user_pages_spec.rb:52:in `block (5 levels) in '

接下来我尝试在不传递变量的情况下运行它user_pages_spec.rbbefore { valid_signup() }并且在函数中没有变量utilities.rb

def valid_signup()
    fill_in "Name",     with: "Example User"
    fill_in "Email",    with: "user@example.com"
    fill_in "Password", with: "foobar"
    fill_in "Confirmation", with: "foobar"
end

这返回:

Failures:

  1) UserPages signup with valid information should create a user
     Failure/Error: before { valid_signup(user) }
     NameError:
       undefined local variable or method `user' for #
     # ./spec/requests/user_pages_spec.rb:42:in `block (4 levels) in '

  2) UserPages signup with valid information after saving the user 
     Failure/Error: it { should have_selector('title', text: user.name) }
     NoMethodError:
       undefined method `name' for nil:NilClass
     # ./spec/requests/user_pages_spec.rb:52:in `block (5 levels) in '

仍然没有更接近答案。我可能忽略了一些简单的事情。不知道是什么。不过,我得到了我第一次做错的事情:我只是认为 FactoryGirl 是一种创建变量的方法,但我不知道它实际上对我的测试数据库做了什么。

4

4 回答 4

3

我将尝试解释您的原始测试中发生了什么(我发现它比编辑后的版本更容易修复):

describe "with valid information" do
  let(:user) {FactoryGirl.build(:user)} # FactoryGirl.create will save the instance, you should be using build instead
  before { valid_signup(user) }

  it "should create a user" do
    expect { click_button submit }.to change(User, :count).by(1)
  end

  describe "after saving the user" do
    before { click_button submit }
    # let(:user) { User.find_by_email(user.email) } # this is not needed any more 

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

有关 FactoryGirl 使用的更多信息:https ://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#using-factories

于 2012-11-29T12:26:33.370 回答
2

FactoryGirl 将用户保存到数据库,然后您使用数据库中的用户访问 sign_in_path 并使用 valid_sigin(user) 填写登录表单

let(:user){FactoryGirl.create(:user)}
before { valid_signin(user) }

当你这样做时:

let(:user){FactoryGirl.create(:user)}
before { valid_signup(user) }

factory girl 将用户保存在数据库中,然后您使用已收到的电子邮件填写表格。

编辑:

  describe "with valid information" do
  before { valid_signup(user) }

您没有定义变量用户,因为您已删除let(:user){FactoryGilr.create(:user)},并且您应该访问正确的路径,您当前的路径是“sign_in_path”并且应该是“sign_up_path”

你应该这样做:

实用程序.rb

def valid_sign_up(user)
  fill_in "Name",         with: user.name
  fill_in "Email",        with: user.email
  fill_in "Password",     with: user.password
  fill_in "Confirmation", with: user.password_confirmation
end

user_pages_spec.rb

describe "with valid information" do
  let(:user){User.new(name: "my name", email: "myemail@example"...)
  before do        
    visit sign_up
    valid_sign_up(user)
  end 

  it "should create a user" do
    expect { click_button submit }.to change(User, :count).by(1)
  end
end
于 2012-11-28T14:49:48.297 回答
2

我遇到了同样的问题并找到了解决方案:当你定义valid_signup时,它应该以'page'作为参数。毕竟,您是在测试页面元素,而不是用户。

规范/支持/实用程序.rb

def valid_signup(page)
  fill_in "Name", with: "Example User"
  fill_in "Email",          with: "user@example.com"
  fill_in "Password",       with: "foobar"
  fill_in "Confirmation",   with: "foobar"
end

规范/请求/user_pages_spec.rb

在 { valid_signup(page) } 之前描述“具有有效信息”

  it "should create a user" do
    expect { click_button submit }.to change(User, :count).by(1)
  end

我希望这有帮助!

更新我现在意识到这是因为变量“页面”的范围而起作用(因为它是主题)。要使用“用户”,我添加了该行

let(:user) { FactoryGirl.create(:user) }

多于

之前{ sign_up(user) }。这打破了后来的规范,我也尝试使用“用户”作为变量,所以我将名称更改为“编辑用户”。这是完整的示例:

user_pages_spec.rb

require 'spec_helper'

describe "UserPages" do

  subject { page }

...

  describe "signup page" do
    before { visit signup_path }

    let(:submit) { "Create my account" }

    it { should have_selector('h1', text: 'Sign up') }
    it { should have_selector('title', text: full_title('Sign up')) }

  describe "with invalid information" do
    it "should not create a user" do
    expect { click_button submit }.not_to change(User, :count)
  end

  describe "after submission" do
    before { click_button submit }

    it { should have_selector('title', text: 'Sign up') }
    it { should have_content('error') }
  end
end

  describe "with valid information" do
    let(:user) { FactoryGirl.create(:user) }
    before { sign_up(user) }

    it "should create a user" do
      expect { click_button submit }.to change(User, :count).by(1)
    end

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

      it { should have_selector('title', text: editeduser.name) }
      it { should have_selector('div.alert.alert-success', text: 'Welcome') }
      it { should have_link('Sign out') }
    end
  end
end

希望这可以帮助某人!

于 2013-01-30T03:11:33.937 回答
0

我也对这个很好奇,并找到了一个可能更符合 Hartl 期望的答案(尽管只是在学习,我不能 100% 确定最佳答案是否更优雅)。

由于我们没有使用 FactoryGirl 来注册用户,而是让他们登录,所以我不想在重构中使用它。这就是我的实用程序.rb 中的内容:

def valid_signup
  fill_in "Name",         with: "Example User"
  fill_in "Email",        with: "user@example.com"
  fill_in "Password",     with: "foobar"
  fill_in "Confirmation", with: "foobar"
end

然后在 user_pages_spec.rb 我替换

  describe "with valid information" do
  before do
    fill_in "Name",           with: "Example User"
    fill_in "Email",          with: "user@example.com"
    fill_in "Password",       with: "foobar"
    fill_in "Confirmation",   with: "foobar"
  end

  describe "with valid information" do
  before { valid_signup } 

我们不需要为了检查一次性注册而将用户保存到数据库中,因为他们不需要通过多个页面视图持续存在。此外,由于我们不查找用户,因此在 valid_signup 方法之后我们不需要 (user) 参数(我认为我的术语是正确的。如果我没有,请纠正我。)

于 2012-12-24T18:15:37.963 回答