【问题标题】:Rails 4 Testing with RSpec and FactoryGirl, nested attributes not savingRails 4 使用 RSpec 和 FactoryGirl 进行测试,嵌套属性不保存
【发布时间】:2015-04-23 23:09:25
【问题描述】:

我一直在强迫自己学习带有 BDD 的 Rails4,到目前为止,它的表现非常好。但是,我现在已经在这个问题上苦苦挣扎了几个小时,但没有想出它为什么不起作用的原因。我目前正在测试控制器,只构建控制器和模型,没有表单或其他任何东西。它通过控制台工作,所以我一定是使用了错误的东西。

这是我的参考代码:

/admin/pages_controller.rb

class Admin::PagesController < ApplicationController

  def update
    @page = Page.find_by_id(params[:id])

    @page.update_attributes!(message_params)

    redirect_to edit_admin_page_path(@page)
  end

  private
   def message_params
      params.require(:page).permit(
        :url, 
        :position, 
        :name, 
        :tags,
        images_attributes:
            [:image_file_name, :image_file_size, :image_content_type, :name, :caption, :tags, :owner_id, :owner_type],
        block_attributes:
            [:id, :body, :owner_id, :owner_type]
      )
   end

end

models/page.rb

class Page < ActiveRecord::Base
    validates :name, presence: true
    before_save :validate_url

    has_many :images, :as => :owner 
    has_one :block, :as => :owner

    accepts_nested_attributes_for :images, :block

    def validate_url
        if url.blank?
          self.url = self.name.strip.downcase.gsub(" ","-").gsub(%r([^0-9a-z-]), '').gsub("--","-")
        end
    end
end

pages_controller_spec.rb

  describe "POST #update" do
    before :each do
        @page = FactoryGirl.create(:page)
    end

    it "makes sure user can upload an image" do
        image = FactoryGirl.build(:image, owner_id: @page.id, owner_type: "Page") 
        post :update, id: @page, page: { :images => [ image ]}
        @page.reload
        expect(@page.images.first).to eq(image)
    end

    it "updates values of the attributes accordingly" do
        post :update, id: @page, page: { :name => 'foo', :url => 'bar' }
        @page.reload
        expect(@page.name).to eq('foo')
    end

    it "updates the values of the block" do
        @page = FactoryGirl.create(:page_with_block)
        post :update, id: @page, page: { block: { :body => 'foobar' } }
        @page.reload
        expect(@page.block.body).to eq('foobar')        
    end

  end

spec/factories/blocks.rb

FactoryGirl.define do
  factory :block do
    body "<html><body><h1>Hello world!</h1><section>This is the content section</section></body></html>"
  end  
end

spec/factories/images.rb

include ActionDispatch::TestProcess

FactoryGirl.define do
  factory :image do
    image { fixture_file_upload Rails.root.to_s + '/spec/images/1.jpg', 'image/jpg'}
    name { Faker::App.name }
    caption { Faker::Lorem.sentence }
    tags { Faker::Lorem.words }
  end

end

RSpec 结果:

  1) Admin::PagesController POST #update makes sure user can upload an image
     Failure/Error: expect(@page.images.first).to eq(image)

       expected: #<Image id: nil, image_file_name: "1.jpg", image_file_size: 15078, image_content_type: "image/jpg", name: "Alpha", caption: "Assumenda et exercitationem quo.", tags: ["doloribus", "maiores", "dicta"], owner_id: 37, owner_type: "Page", created_at: nil, updated_at: nil>
            got: nil

       (compared using ==)
     # ./spec/controllers/admin/pages_controller_spec.rb:111:in `block (3 levels) in <top (required)>'

  2) Admin::PagesController POST #update updates the values of the block
     Failure/Error: expect(@page.block.body).to eq('foobar')

       expected: "foobar"
            got: "<html><body><h1>Hello world!</h1><section>This is the content section</section></body></html>"

       (compared using ==)
     # ./spec/controllers/admin/pages_controller_spec.rb:124:in `block (3 levels) in <top (required)>'

Finished in 3.95 seconds (files took 3.71 seconds to load)
19 examples, 2 failures

models/image.rb

class Image < ActiveRecord::Base
    validates_presence_of :image_file_name, :image_file_size, :image_content_type
    before_save :clean_up

    belongs_to :page, foreign_key: "owner_id"

    has_attached_file :image
    validates_attachment_content_type(:image, :content_type => /^image\/(jpg|jpeg|pjpeg|png|x-png|gif)$/)
    validates :image, :attachment_presence => true


    def clean_up
        if name.blank?
          self.name = self.image_file_name.strip.downcase.gsub(" ","-").gsub(%r([^0-9a-z-]), '').gsub("--","-")
        end
    end
end

对此问题的任何帮助将不胜感激,因为我发现的有关此主题的大多数搜索都涉及表单或 params.require。我可以使用第二个规范更新页面本身的属性,但我所做的任何事情似乎都无法使其成为嵌套资源。我在这里想念什么?提前致谢!

编辑:更新以包含图像模型

【问题讨论】:

  • 您忘记发帖Image 班级。发布它,我会让你知道你应该做什么。
  • @ole 已发布。抱歉,我忘记添加那个了。

标签: ruby-on-rails ruby rspec factory-bot ruby-on-rails-4.1


【解决方案1】:

首先,避免使用factory_girlhttp://pivotallabs.com/avoid-using-fixture-file-upload-with-factorygirl-and-paperclip/的fixture文件上传

你可以像下面这样简单地使用它:

FactoryGirl.define do
  factory :image do
    image { File.new(Rails.root.join('spec/images/1.jpg') }
    name { Faker::App.name }
    caption { Faker::Lorem.sentence }
    tags { Faker::Lorem.words }
  end
end

这不是一个明确的答案,它只是朝着正确的方向前进:

describe "POST #update" do
  let(:img) { Rack::Test::UploadedFile.new("spec/factories/files/file.csv") } 
  let!(:page) { FactoryGirl.create(:page) }
  let(:image_attrs) do 
    FactoryGirl.attributes_for(:image, owner: page)
  end
  let(:image) { page.images.first }

  it "makes sure user can upload an image" do
    expect do
      post :update, id: page, page: { :images_attributes => { "0" => image_params } }
    end.to change { page.reload.images.count }.by(1)

    # Now compare the attributes
    expect(image.content_type).to eq(image_attrs[:some_type])
    # ...
  end

  it "updates values of the attributes accordingly" do
    expect do
      post :update, id: page, page: { name: 'foo', url: 'bar' }
    end.to change { page.reload.name }.to("foo")
  end

  context "updates the values of the block" do
    let(:page) { FactoryGirl.create(:page_with_block) }

    it do
      expect do
        post :update, id: page, page: { block: { :body => 'foobar' } }
      end.to change { page.reload.block.body }.to("foobar")
    end
  end
end

【讨论】:

  • 我一直在使用夹具上传,因为每次我在 system/images 文件夹中运行规范时,它都会创建几十张图像,但我现在添加了一个清理方法,所以没关系。我试过你的代码,它没有改变任何东西,除了让我意识到用外键而不是多态来指定图像是多么愚蠢。我按照您的说明实现了其余部分,但是嵌套属性都没有保存。
【解决方案2】:

由于评论而将规范更改为:

describe "POST #update" do
let(:img) { Rack::Test::UploadedFile.new('spec/images/1.jpg') }
let(:image_attrs) do 
    FactoryGirl.attributes_for(:image, owner: page, image: img)
end
let!(:page) { FactoryGirl.create(:page_with_block) }


it "makes sure user can upload an image" do
    expect do
      post :update, id: page, page: { :images => { "0" => image_attrs } }
    end.to change { page.reload.images.count }.by(1)
end

it "updates values of the attributes accordingly" do
    post :update, id: page, page: { :name => 'foo', :url => 'bar' }
    page.reload
    expect(page.name).to eq('foo')
end

it "updates the values of the block" do
    expect do
        post :update, id: page, page: { block: { :body => 'foobar' } }
    end.to change { page.reload.block.body }.to("foobar")   
end

结束

问题依旧:

................F.F

Failures:

  1) Admin::PagesController POST #update makes sure user can upload an image
     Failure/Error: expect do
       expected result to have changed by 1, but was changed by 0
     # ./spec/controllers/admin/pages_controller_spec.rb:111:in `block (3 levels) in <top (required)>'

  2) Admin::PagesController POST #update updates the values of the block
     Failure/Error: expect do
       expected result to have changed to "foobar", but did not change
     # ./spec/controllers/admin/pages_controller_spec.rb:123:in `block (3 levels) in <top (required)>'

Finished in 1.32 seconds (files took 1.34 seconds to load)
19 examples, 2 failures

Failed examples:

rspec ./spec/controllers/admin/pages_controller_spec.rb:110 # Admin::PagesController POST #update makes sure user can upload an image
rspec ./spec/controllers/admin/pages_controller_spec.rb:122 # Admin::PagesController POST #update updates the values of the block

编辑: 事实证明,我没有正确地允许 image_attributes 与回形针一起正常工作,而且基本上很多小事情都是错误的。我更习惯于使用 Rails 3 进行设置,并误解了 Rails 4 gems 的工作方式。

更新规格:

  describe "POST #update" do
    let(:img) { Rack::Test::UploadedFile.new(Rails.root.join('spec/images/1.jpg'), 'image/jpg') }
    let(:image_attrs) do 
        FactoryGirl.attributes_for(:image, owner: page, image: img)
    end
    let(:block_attrs) do
        FactoryGirl.attributes_for(:block, owner: page, :body => 'foobar')
    end
    let!(:page) { FactoryGirl.create(:page_with_block) }


    it "makes sure user can upload an image" do
        expect do
          post :update, id: page, page: { :images_attributes => { "0" => image_attrs } }
        end.to change { page.reload.images.count }.by(1)
    end

    it "updates values of the attributes accordingly" do
        post :update, id: page, page: { :name => 'foo', :url => 'bar' }
        page.reload
        expect(page.name).to eq('foo')
    end

    it "updates the values of the block" do
        expect do
            post :update, id: page, page: { :block_attributes => block_attrs }
        end.to change { page.reload.block.body }.to("foobar")   
    end
  end

感谢@ole 为我指明了测试代码的正确方向!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多