RSpec 测试中出现问题 - 保存父记录两次

Trouble in RSpec test - saving parent record twice

我是 Rails 编程新手,我的 RSpec 测试在尝试保存嵌套记录时失败了。测试日志表明 ActiveRecord 尝试两次保存父级,这导致静默数据库保存失败。

sign_up_spec.rb(抱歉遗漏!)

require 'rails_helper'

RSpec.feature "Accounts", type: :feature do
  let(:user) { FactoryGirl.create(:user) }

  scenario "creating an account" do
    visit root_path
    click_link "Sign Up"
    fill_in "Account name", :with => "Test Firm"
    fill_in "Username", :with => user.username
    fill_in "Password", :with => user.password
    fill_in "Password confirmation", :with => user.password_confirmation
    click_button "Create Account"
    success_message = "Your account has been successfully created."
    expect(page).to have_content(success_message)
    expect(page).to have_content("Signed in as #{user.username}")
  end
end

account.rb

class Account < ActiveRecord::Base
  belongs_to :owner, :class_name => "User"
  accepts_nested_attributes_for :owner
end

user.rb

class User < ActiveRecord::Base
  has_secure_password
  validates :username, presence: true, uniqueness: { case_sensitive: false }
  validates :password, presence: true, length: { minimum: 8, maximum: 20 }
end

account_controller.rb

class AccountsController < ApplicationController
  def new
    @account = Account.new
    @account.build_owner
  end

  def create
    account = Account.create(account_params)
    env["warden"].set_user(account.owner, :scope => :user)
    env["warden"].set_user(account, :scope => :account)
    flash[:success] = "Your account has been successfully created."
    redirect_to root_url
  end

  private

    def account_params
     params.require(:account).permit(:account_name, {:owner_attributes => [
        :username, :password, :password_confirmation
        ]})
    end
end

帐号new.html.erb

<h2>Sign Up</h2>
<%= form_for(@account) do |account| %>
  <p>
    <%= account.label :account_name %><br>
    <%= account.text_field :account_name %>
  </p>
  <%= account.fields_for :owner do |owner| %>
    <p>
      <%= owner.label :username %><br>
      <%= owner.text_field :username %>
    </p>
    <p>
      <%= owner.label :password %><br>
      <%= owner.password_field :password %>
    </p>
    <p>
      <%= owner.label :password_confirmation %><br>
      <%= owner.password_field :password_confirmation %>
    </p>
  <% end %>
  <%= account.submit %>
<% end %>

以下测试结果日志的摘录表明 ActiveRecord 试图插入用户记录两次,一次是在 Account#new 函数中,另一次是在 Account#create 函数中。我根据我对整个过程中发生的事情的理解添加了评论:

测试日志

Started GET "/sign_up" for 127.0.0.1 at 2015-06-17 15:50:43 +0000
Processing by AccountsController#new as HTML
Rendered accounts/new.html.erb within layouts/application (45.6ms)
Completed 200 OK in 110ms (Views: 51.6ms | ActiveRecord: 6.9ms)

# it appears that this action is happening from the Account#new method;
# why would @account.build_owner cause a database action?
(0.4ms)  SAVEPOINT active_record_1
User Exists (30.8ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."username") = LOWER('user1') LIMIT 1
SQL (3.8ms)  INSERT INTO "users" ("username", "password_digest", "created_at", "updated_at") VALUES (, , , ) RETURNING "id"  [["username", "user1"], ["password_digest", "blahblah"], ["created_at", "2015-06-17 13:15:40.667965"], ["updated_at", "2015-06-17 13:15:40.667965"]]
(1.4ms)  RELEASE SAVEPOINT active_record_1
Started POST "/accounts" for 127.0.0.1 at 2015-06-17 13:15:40 +0000
Processing by AccountsController#create as HTML
Parameters: {"utf8"=>"✓", "account"=>{"account_name"=>"Test Firm", "owner_attributes"=>{"username"=>"user1", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}, "commit"=>"Create Account"}

# the Account#create function wants to add the user a second time (or more
# accurately, the first time), but the user parent already exists from above
# so it fails the uniqueness test
(0.2ms)  SAVEPOINT active_record_1
User Exists (0.9ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."username") = LOWER('user1') LIMIT 1

# which causes the query to bomb
(0.2ms)  ROLLBACK TO SAVEPOINT active_record_1

#and neither the parent or child records are created
#<Account id: nil, account_name: nil, created_at: nil, updated_at: nil, owner_id: nil>
#<User id: nil, username: "user1", created_at: nil, updated_at: nil, password_digest: "blahblah">
Redirected to http://www.example.com/
Completed 302 Found in 17ms (ActiveRecord: 1.3ms)
Started GET "/" for 127.0.0.1 at 2015-06-17 13:15:40 +0000
Processing by StaticPagesController#home as HTML
Rendered static_pages/home.html.erb within layouts/application (0.5ms)
Completed 200 OK in 6ms (Views: 5.3ms | ActiveRecord: 0.0ms)
(4.3ms)  ROLLBACK

任何人都可以帮助我了解我所缺少的吗?谢谢

问题在 sign_up_spec.rb。你的测试有一个 let for user,这意味着你第一次在你的测试中提到 user 时它会创建一个 user。但是,您的应用程序代码应该自行创建用户。

正如我在评论中所说,这就是为什么在您的测试日志中您看到它成功完成了 get 请求,然后 post 请求,user 被创建。当您的代码在提交表单后尝试创建一个 user 时,它已经被创建了!

我评论了你的测试规范以使其更清楚:

let(:user) { FactoryGirl.create(:user) }
scenario "creating an account" do
    visit root_path
    click_link "Sign Up" # this is the successful get request
    fill_in "Account name", :with => "Test Firm"
    fill_in "Username", :with => user.username # this triggers the let and creates a user
    fill_in "Password", :with => user.password
    fill_in "Password confirmation", :with => user.password_confirmation
    click_button "Create Account" # this starts the post request
    success_message = "Your account has been successfully created."
    expect(page).to have_content(success_message)
    expect(page).to have_content("Signed in as #{user.username}")
end

要解决此问题,只需将 let 语句更改为使用 FactoryGirl 的 attributes_for 方法,并使用 with: user[:username] 而不是 with: user.username

此外,您正在以嵌套形式传递 owner 的符号版本,这意味着 Rails 将构建 owner 的新实例,然后将其分配给@account。但是,您已经在 new 操作中为 @account 构建了一个 owner 实例。所以这段代码是多余的。从控制器操作中删除 build_owner 行,或者将 @account.owner 传递给视图中的 fields_for 调用。