0
votes

I am building a simple blog app in order to learn BDD/TDD with RSpec and Factory Girl. Through this process, I continue to run into 'Failures' but I believe they have more to do with how I am using Factory Girl than anything.

As you'll see below, in order to get my specs to pass, I'm having a hard time keeping my test DRY - there must be something I am misunderstanding. You'll notice, I'm not using Factory Girl to it's full potential and at times, skipping it altogether. I find that I commonly run into problems when using functions such as get :create, get :show, or put :update within the spec.

I am currently stuck on the #PUT update spec that should simply test the assignment of the @post variable. I have tried multiple types of this spec that I found online, yet none seem to work - hence, is it Factory Girl? Maybe the specs I'm finding online are outdated Rspec versions?

I'm using: 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

factories/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

Current Failure:

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

2 Answers

2
votes

You could definitely leverage factories here.

The factory you've created is actually fine too.

Instead of doing: post = Post.create(title: 'Charlie boy', body: 'Bow wow wow ruff')

Do this: post = FactoryGirl.create(:post)

You can get ever more DRY if you do this:

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

This will allow you do this in your spec: post = create(:post)

Regarding your PUT test, try this 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

Edit:

Also, don't be afraid of moving things in to a before :each do if you need to. They are great at keeping things DRY

1
votes

The immediate reason why your spec is failing is because you can only call on the controller once per test, and for update you're calling it twice: in the before-action, you are calling create... and then in the main part of the update test you are calling update... controller specs don't like that.

In order to get the existing spec working, you would need to replace the post :create, post: attributes_for(:post) line in the before-action with just creating a post or (as mentioned already) using factory girl to create a post - rather than trying to do it by calling the controller to do it.