Rails教程:RSpec测试解耦

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

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

例8.3.3:utilities.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.name没有定义,但我没有看到任何原因。 我已经多次重启Guard了,并做了一个rake db:test:prepare确保测试db(使用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) ,因为valid_signupuser变量是不再被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注册有效信息应创建用户失败/错误:在{valid_signup(user)}之前NameError:未定义的局部变量或方法user' for # # ./spec/requests/user_pages_spec.rb:42:in block(4 levels)in’

2)在保存用户失败/错误后,UserPages注册有效信息:它{should have_selector(’title’,text:user.name)} NoMethodError:未定义的方法name' for nil:NilClass # ./spec/requests/user_pages_spec.rb:52:in块(5级)中’

我也尝试使用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.rb传递变量: before { 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是一种创建变量的方法,我不知道它实际上对我的测试数据库做了些什么。

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

 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

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

 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”

你应该做这样的事情:

utilities.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 

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

规格/支持/ utilities.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(页面)}之前描述“有效信息”

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

我希望这有帮助!

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

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

以上

{ sign_up(user) } 。 这打破了后来的规范,我也尝试使用’user’作为变量,因此我将名称更改为’editeduser’。 这是完整的例子:

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 

希望这有助于某人!

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

由于我们没有使用FactoryGirl来注册用户,而是要签名,我不想在我的重构中使用它。 这就是我在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 

然后在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方法之后不需要(用户)参数(我认为我的术语是正确的。如果我没有,请纠正我。)