Rspec控制器测试失败后#create与关联

时间:2017-07-29 21:31:36

标签: ruby-on-rails ruby rspec

我有一个属于用户的客户模型,我对post#created的控制器测试成功。但我有一个属于用户和计划的订阅模型,它失败了(我使用的是rails 5.1.2)。

这是我的规范:

#rspec/controllers/checkout/subscriptions_controller_spec.rb

require 'rails_helper'

RSpec.describe Checkout::SubscriptionsController, type: :controller do
  describe 'POST #create' do
    let!(:user) { FactoryGirl.create(:user) }

    before do
      sign_in user
    end

    context 'with valid attributes' do
      it 'creates a new subscription' do
        expect { post :create, params: { subscription: FactoryGirl.attributes_for(:subscription) } }.to change(Subscription, :count).by(1)
      end
    end
  end
end

订阅控制器:

# app/controllers/checkout/subscriptions_controller.rb

module Checkout
  class SubscriptionsController < Checkout::CheckoutController
    before_action :set_subscription, only: %i[edit update destroy]
    before_action :set_options

    def create
      @subscription = Subscription.new(subscription_params)
      @subscription.user_id = current_user.id

      if @subscription.valid?
        respond_to do |format|
          if @subscription.save
            # some code, excluded for brevity
          end
        end
      else
        respond_to do |format|
          format.html { render :new }
          format.json { render json: @subscription.errors, status: :unprocessable_entity }
        end
      end
    end

    private

    def set_subscription
      @subscription = Subscription.find(params[:id])
    end

    def set_options
      @categories = Category.where(active: true)
      @plans = Plan.where(active: true)
    end

    def subscription_params
      params.require(:subscription).permit(:user_id, :plan_id, :first_name, :last_name, :address, :address_2, :city, :state, :postal_code, :email, :price)
    end
  end
end

订阅模式 -

# app/models/subscription.rb

class Subscription < ApplicationRecord
  belongs_to :user
  belongs_to :plan
  has_many :shipments

  validates :first_name, :last_name, :address, :city, :state, :postal_code, :plan_id, presence: true

  before_create :set_price
  before_update :set_price
  before_create :set_dates
  before_update :set_dates

  def set_dates
    # some code, excluded for brevity
  end

  def set_price
    # some code, excluded for brevity
  end
end

我还为我的模型使用了一些FactoryGirl工厂。

# spec/factories/subscriptions.rb

  FactoryGirl.define do
    factory :subscription do
      first_name Faker::Name.first_name
      last_name Faker::Name.last_name
      address Faker::Address.street_address
      city Faker::Address.city
      state Faker::Address.state_abbr
      postal_code Faker::Address.zip
      plan
      user
    end
  end

# spec/factories/plans.rb

FactoryGirl.define do
  factory :plan do
    name 'Nine Month Plan'
    description 'Nine Month Plan description'
    price 225.00
    active true
    starts_on Date.new(2017, 9, 1)
    expires_on Date.new(2018, 5, 15)
    monthly_duration 9
    prep_days_required 5
    category
  end
end

# spec/factories/user.rb

FactoryGirl.define do
  factory :user do
    name Faker::Name.name
    email Faker::Internet.email
    password 'Abcdef10'
  end
end

当我查看日志时,我注意到在运行规范和创建订阅时没有填充用户和计划,这必然是它失败的原因,因为需要计划。但我无法弄清楚如何解决这个问题。有任何想法吗?提前谢谢。

1 个答案:

答案 0 :(得分:1)

问题是,根据您的模型定义,您只能创建与现有Subscription相关联的Plan

class Subscription < ApplicationRecord
  belongs_to :plan

  validates :plan_id, presence: true
end

您可以通过在rspec测试中设置断点并检查response.body来调试此问题;或类似地通过在SubscriptionsController#create中设置断点并检查@subscription.errors。无论哪种方式,您都应该看到plan_id不存在的错误(因此@subscription没有保存)。

问题源于FactoryGirl#attributes_for does not include associated model IDs。 (此问题实际上在项目中been raised many times,并进行了详细讨论。)

可以只是在测试的请求有效负载中显式传递plan_id,以使其通过:

it 'creates a new subscription' do
  expect do
    post(
      :create,
      params: {
        subscription: FactoryGirl.attributes_for(:subscription).merge(post_id: 123)
      }
  end.to change(Subscription, :count).by(1)
end

但是,这种解决方案有些艰巨且容易出错。我建议的一个更通用的替代方法是定义以下规范帮助方法:

def build_attributes(*args)
  FactoryGirl.build(*args).attributes.delete_if do |k, v| 
    ["id", "created_at", "updated_at"].include?(k)
  end
end

这利用了build(:subscription).attributes 包含外键的事实,因为它引用了关联。

然后您可以按如下方式编写测试:

it 'creates a new subscription' do
  expect do
    post(
      :create,
      params: {
        subscription: build_attributes(:subscription)
      }
    )
  end.to change(Subscription, :count).by(1)
end

请注意,此测试仍然有点不切实际,因为Post实际上并不存在于数据库中!现在,这可能没问题。但是在将来,您可能会发现SubscriptionController#create操作实际上需要查找关联的Post作为逻辑的一部分。

在这种情况下,您需要在测试中明确创建Post

let!(:post) { create :post }
let(:subscription) { build :subscription, post: post }

...然后将subscription.attributes发送给控制器。