如何用Factory Girl创建DRYer Rspec

时间:2015-03-10 00:50:07

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

我正在构建一个简单的博客应用程序,以便使用RSpec和Factory Girl学习BDD / TDD。通过这个过程,我继续遇到“失败”,但我相信他们更多地与我使用工厂女孩的方式有关。

正如您将在下面看到的,为了让我的规格通过,我很难保持我的测试DRY - 必须有一些我误解的东西。你会注意到,我没有使用Factory Girl来充分发挥它的潜力,有时甚至完全跳过它。我发现在规范中使用get :createget :showput :update等函数时,我常遇到问题。

我目前停留在#PUT update规范上,应该只测试@post变量的赋值。我尝试过在网上找到的这种规格的多种类型,但似乎都没有用 - 因此,它是Factory Girl吗?也许我在网上找到的规格是过时的Rspec版本?

我正在使用: Rspec 3.1.7 Rails 4.1.6

posts_controller_spec.rb

require 'rails_helper'
require 'shoulda-matchers'

RSpec.describe PostsController, :type => :controller do


    describe "#GET index" do
            it 'renders the index template' do
                get :index
                expect(response).to be_success
            end

            it "assigns all posts as @posts" do
                post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')
                get :index
                expect(assigns(:posts)).to eq([post])
            end
    end


    describe '#GET show' do
            it 'assigns the request post to @post' do
                post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
                get :show, id: post.id
                expect(assigns(:post)).to eq(post)
            end
    end



    describe '#GET create' do
        context 'with valid attributes' do
          before :each do
            post :create, post: attributes_for(:post)
          end

              it 'creates the post' do
                expect(Post.count).to eq(1)
                expect(flash[:notice]).to eq('Your post has been saved!')
              end

              it 'assigns a newly created post as @post' do
                expect(assigns(:post)).to be_a(Post)
                expect(assigns(:post)).to be_persisted
              end

              it 'redirects to the "show" action for the new post' do
                expect(response).to redirect_to Post.first
              end
        end


        context 'with invalid attributes' do
            before :each do
                post :create, post: attributes_for(:post, title: 'ha')
            end

                it 'fails to create a post' do
                    expect(Post.count).to_not eq(1)
                    expect(flash[:notice]).to eq('There was an error saving your post.')
                end

                it 'redirects to the "new" action' do
                    expect(response).to redirect_to new_post_path
                end
        end
    end



    describe '#GET edit' do
            it 'assigns the request post to @post' do
                post = Post.create!(title: 'Charlie boy', body: 'Bow wow wow ruff')
                get :edit, id: post.id
                expect(assigns(:post)).to eq(post)
            end
    end

    describe '#PUT update' do
        context 'with success' do
          before :each do
            post :create, post: attributes_for(:post)
          end

            it 'assigns the post to @post' do
                put :update, id: post.id
                expect(assigns(:post)).to eq(post)
            end
        end
    end

end

posts_controller.rb

class PostsController < ApplicationController

    def index
        @posts = Post.all.order('created_at DESC')
    end


    def new
        @post = Post.new
    end


    def create
        @post = Post.new(post_params)

        if @post.save
            flash[:notice] = "Your post has been saved!"
            redirect_to @post
        else
            flash[:notice] = "There was an error saving your post."
            redirect_to new_post_path
        end
    end


    def show
        @post = Post.find(params[:id])
    end


    def edit
        @post = Post.find(params[:id])
    end


    def update
        @post = Post.find(params[:id])

        # if @post.update(params[:post].permit(:title, :body))
        #   flash[:notice] = "Your post is updated!"
        #   redirect_to @post
        # else
        #   flash[:notice] = "There was an error updating your post."
        #   render :edit
        # end
    end



    private

    def post_params
        params.require(:post).permit(:title, :body)
    end
end

工厂/ post.rb

FactoryGirl.define do
    factory :post do
        title 'First title ever'
        body 'Forage paleo aesthetic food truck. Bespoke gastropub pork belly, tattooed readymade chambray keffiyeh Truffaut ennui trust fund you probably haven\'t heard of them tousled.'
    end
end

当前失败:

Failures:

  1) PostsController#PUT update with success assigns the post to @post
     Failure/Error: put :update, id: post.id
     ArgumentError:
       wrong number of arguments (0 for 1+)
     # ./spec/controllers/posts_controller_spec.rb:86:in `block (4 levels) in <top (required)>'

Finished in 0.19137 seconds (files took 1.17 seconds to load)
17 examples, 1 failure

2 个答案:

答案 0 :(得分:2)

你绝对可以在这里利用工厂。

你创建的工厂实际上也很好。

而不是: post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')

执行此操作:post = FactoryGirl.create(:post)

如果你这样做,你可以得到更多的干:

# in spec/rails_helper.rb
RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

这样您就可以在规范post = create(:post)

中执行此操作

关于您的PUT测试,请尝试此from a previous SO answer

describe '#PUT update' do
  let(:attr) do 
    { :title => 'new title', :content => 'new content' }
  end

  context 'with success' do
    before :each do
      @post = FactoryGirl.create(:post)
    end

    it 'assigns the post to @post' do
      put :update, :id => @post.id, :post => attr
      @post.reload
      expect(assigns(:post)).to eq(post)
    end
  end
end

编辑:

另外,如果需要,不要害怕将事情移到before :each do。他们非常善于保持干净

答案 1 :(得分:1)

您的规范失败的直接原因是因为您只能在每次测试时调用控制器一次,而对于更新,您将调用它两次:在操作前,您调用create ...然后在您正在调用更新的更新测试的主要部分...控制器规格不喜欢。

为了使现有规范正常工作,您需要使用工厂女孩创建帖子或(如上所述)替换前作用中的post :create, post: attributes_for(:post)行来创建帖子 - 而不是尝试通过调用控制器来做到这一点。