如何使用RSpec在Rails中测试update_attributes函数?

时间:2017-10-29 12:32:48

标签: ruby-on-rails rspec

在我的Rails 4应用中,我有update动作:

class UsersController < ApplicationController

  ...

  def update
    current_email = @user.email
    new_email = user_params[:email].downcase
    if @user.update_attributes(user_params)
      if current_email != new_email
        @user.email = current_email
        @user.new_email = new_email.downcase
        @user.send_email_confirmation_email
        flash[:success] = "Please click the link we've just sent you to confirm your new email address."
      else
        flash[:success] = "User updated."
      end
      redirect_to edit_user_path(@user)
    else
      render :edit
    end
  end

  ...

end

它基本上确保user不能简单地保存任何新的电子邮件地址。他必须首先通过点击我们发送给他的电子邮件中的链接来确认。

这很好用,但由于某些原因我还没有找到测试它的方法。

无论我做什么,以下RSpec测试都会失败:

it "changes the user's new_email attribute" do
  @user = FactoryGirl.create(:user, :email => "john@doe.com")
  patch :update, :id => @user, :user => FactoryGirl.attributes_for(:user, :email => "new@email.com")
  expect(@user.reload.new_email).to eq("new@email.com")
end

@user.new_email始终为nil,测试始终失败。我在这里缺少什么?

重新分解我的update行动根本不会成为问题。也许有更好的方法?谢谢你的帮助。

1 个答案:

答案 0 :(得分:1)

我会像这样编写规范:

let(:user) { FactoryGirl.create(:user, email: "john@doe.com") }

it "changes the user's new_email attribute" do
  expect do
    patch :update, id: @user, user: FactoryGirl.attributes_for(:user, email: "new@email.com")
    user.reload
  end.to change(user, :new_email).from("john@doe.com").to("new@email.com")
end

当涉及到控制器动作本身时,问题是new_email属性永远不会保存到数据库中,除了那种混乱之外。您可以使用跟踪模型中属性更改的ActiveRecord::Dirty来清理它:

class User < ApplicationRecord
  # updates user with attrs but moves a new email to the `new_email`
  # column instead
  def update_with_email(attrs, &block)
    update(attrs) do |record|
      if record.email_changed?
        record.new_email = record.email.downcase
        record.restore_attribute!(:email)
      end
      # keeps the method signature the same as the normal update
      yield record if block_given?
    end
  end
end

将此业务逻辑放在模型中也可让您分别进行测试:

RSpec.describe User, type: :model do
  describe "#update_with_email" do
    let(:user) { FactoryGirl.create(:user) }

    it "does not change the email attribute" do
      expect do
        user.update_with_email(email: ”xxx@example.com”)
        user.reload
      end.to_not change(user, :email)
    end

    it "updates the new_email" do
      expect do
        user.update_with_email(email: ”xxx@example.com”)
        user.reload
      end.to change(user, :new_email).to('xxx@example.com')
    end
  end
end

这可以让你保持控制器好看和瘦:

def update
  if @user.update_with_email(user_params)
    if @user.new_email_changed?
      @user.send_email_confirmation_email
      flash[:success] = "Please click the link we've just sent you to confirm your new email address."
    else
      flash[:success] = "User updated."
    end
    # You probably want to redirect the user away from the form instead.
    redirect_to edit_user_path(@user)
  else
    render :edit
  end
end