2017-07-29 94 views
1

我有一個屬於用戶的客戶模型,並且我的控制器測試用於創建成功。但是我有一個屬於用戶和計劃的訂閱模型,並且它失敗了(我使用rails 5.1.2)。Rspec控制器測試失敗發佈#與關聯創建

這裏是我的規格:

#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 

當我看看日誌,我注意到,用戶和計劃運行的規範和創建訂閱,它必須是爲什麼它的失敗時不被填充,由於需要規劃。但我無法弄清楚如何解決這個問題。有任何想法嗎?提前致謝。

+0

問題是隻能爲已經存在**的User和Post創建一個Subscription,對嗎?因此,除非您想要更改控制器以接受用於創建這些關聯對象的其他參數,否則在發送測試**中的'post'請求之前,您需要先製作它們。 –

+0

我會在下面寫出這個答案,如果我的猜測是正確的,從一瞥代碼...這將是一個努力,我完全重新創建這個本地:)基本上,你只需要例如在spec上下文塊中添加'let!(:post){create:post,user:user)}'。 –

+0

欲瞭解更多詳情,請參閱'FactoryGirl'項目中的討論:https://github.com/thoughtbot/factory_girl/issues/359 –

回答

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這一事實。 (這個問題實際上beenraisedmanytimes擁有該項目,並詳細討論)

可以只是明確地傳遞一個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到控制器。

+0

謝謝湯姆 - 我添加了let!(:plan){FactoryGirl.create(:plan)}和... subscription.merge(plan:plan),改變它plan_id:plan.id做到了。 – Steve

+0

哦......對不起,我在我的答案中混淆了'post'和'plan'之間!很高興我能幫你弄清楚。 –