DarkSun
DarkSun

Reputation: 449

'undefined method' post with Rails and Rspec

Got stuck with:

' undefined method `post' for #<Class:0x000001058c0f68> (NoMethodError)'

on testing controller create action.

I'm using Rails 4, rpsec, and Factory Girl

Controller:

def create
  @post = Post.new(post_params)
  @post.user_id = current_user.id

  if @post.save
    flash[:success] = "Yay! Post created!"
    redirect_to root_path
  else
    # flash[:error] = @post.errors.full_messages
    render 'new'
  end
end

Test:

describe '#create' do
  post 'create',  FactoryGirl.attributes_for(:post, user: @user)
  response.should be_successful
end

Upvotes: 1

Views: 5437

Answers (3)

Richard
Richard

Reputation: 4067

Sorry for being off-topic but I just want to give you some advice.

Consider following best practices and use RSpec's expect syntax instead of should. Read more about why the should syntax is a bad idea here: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax

This is how I would rewrite your example:

describe 'create' do
  it 'responds with 201' do
    post :create, attributes_for(:post, user: @user)
    expect(response.status).to eq(201)
  end
end

In the example I'm using FactoryGirl's short syntax method attributes_for instead of FactoryGirl.attributes_for, it saves a few bytes. Here's how to make the short methods available (in spec/test_helper.rb):

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

I'm testing for the status code 201 which Rails will return by default for a successful create action (redirect should be 3xx).This makes the test more specific.

Hope it's any help for writing better specs.

Upvotes: 2

Pierre-Louis Gottfrois
Pierre-Louis Gottfrois

Reputation: 17631

The issue comes from the fact that post should be used inside an it statement. I usually test my controllers like this:

describe 'POST "create"' do

  let(:user)   { User.new }
  let(:params) { FactoryGirl.attributes_for(:post, user: user) }
  let(:action) { post :create, params }
  let!(:post)  { Post.new }

  before do
    Post.should_receive(:new).and_return(post)
  end

  context 'on success' do

    before do
      post.should_receive(:save).and_return(true)
    end

    it 'renders success' do
      action
      expect(response).to be_success
    end

    it 'redirects' do
      action
      expect(response).to be_redirected
    end

    it 'sets flash message' do
      action
      expect(flash[:success]).to_not be_empty
    end

  end

  context 'on failure' do

    before do
      post.should_receive(:save).and_return(false)
    end

    it 'renders new' do
      action
      expect(response).to render_template(:new)
    end

  end
end

Upvotes: 0

Marek Lipka
Marek Lipka

Reputation: 51151

I think post method is accessible inside it method block:

describe 'create' do
  it 'should be successful' do
    post :create, FactoryGirl.attributes_for(:post, user: @user)
    response.should be_success
  end
end

BTW I think you need to test for redirect, not success status.

Upvotes: 5

Related Questions