Using Rspec to send post on create method - ruby-on-rails

I am trying to use Rspec to send a post on my create action, I have simple controller logic. This is an api app, i run my rspec and it returns with a 422 status. I am trying to figure out whether or not my spec is sending information to the controller. These are the controller/specs in question, how do i insert information into the create method. So far all tutorials seem to be using the same syntax as mine. Thanks in advance
Ignore the hardcoded user_id in my factory, still learning factories and fixing dependency ones
#post_controller
def create
if User.exists?(params[:user_id])
#post = Post.new(post_params)
#post.save
render json: #post, status: 200
else
render status: 422
end
end
#spec
it "saves a new post in the database" do
attrs = attributes_for(:post)
post :create, post: attrs
expect(response.status).to eq(200)
end
#factory
FactoryGirl.define do
factory :post do
title "This is a new title"
body "This is the body"
user_id 1
end

Maybe is your user_id in params[:post][:user_id]?

Related

How to test that a class is called in a controller method with RSpec

I am testing my controller to ensure that a library class is called and that the functionality works as expected. NB: This might have been asked somewhere else but I need help with my specific problem. I would also love pointers on how best to test for this.
To better explain my problem I will provide context through code.
I have a class in my /Lib folder that does an emission of events(don't mind if you don't understand what that means). The class looks something like this:
class ChangeEmitter < Emitter
def initialize(user, role, ...)
#role = role
#user = user
...
end
def emit(type)
case type
when CREATE
payload = "some payload"
when UPDATE
payload = "some payload"
...
end
send_event(payload, current_user, ...)
end
end
Here is how I am using it in my controller:
class UsersController < ApplicationController
def create
#user = User.new(user_params[:user])
if #user.save
render :json => {:success => true, ...}
else
render :json => {:success => false, ...}
end
ChangeEmitter.new(#user, #user.role, ...).emit(ENUMS::CREATE)
end
end
Sorry if some code doesn't make sense, I am trying to explain the problem without exposing too much code.
Here is what I have tried for my tests:
describe UsersController do
before { set_up_authentication }
describe 'POST #create' do
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
post :create, user: user_params
expect(response.status).to eq(200)
// Here is the test for the emitter
expect(ChangeEmitter).to receive(:new)
end
end
end
I expect the ChangeEmitter class to receive new since it is called immediately the create action is executed.
Instead, here is the error I get:
(ChangeEmitter (class)).new(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
What am I missing in the above code and why is the class not receiving new. Is there a better way to test the above functionality? Note that this is Rspec. Your help will be much appreciated. Thanks.
You need to put your expect(ChangeEmitter).to receive(:new) code above the post request. When you are expecting a class to receive a method your "expect" statement goes before the call to the controller. It is expecting something to happen in the future. So your test should look something like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
expect(ChangeEmitter).to receive(:new)
post :create, user: user_params
expect(response.status).to eq(200)
end
EDIT
After noticing that you chain the "emit" action after your call to "new" I realized I needed to update my answer for your specific use case. You need to return an object (I usually return a spy or a double) that emit can be called on. For more information on the difference between spies and doubles check out:
https://www.ombulabs.com/blog/rspec/ruby/spy-vs-double-vs-instance-double.html
Basically a spy will accept any method called on it and return itself whereas with a double you have to specify what methods it can accept and what is returned. For your case I think a spy works.
So you want to do this like:
it "calls the emitter" do
user_params = FactoryGirl.attributes_for(:user)
emitter = spy(ChangeEmitter)
expect(ChangeEmitter).to receive(:new).and_return(emitter)
post :create, user: user_params
expect(response.status).to eq(200)
end

RSpec Controller test, testing a post action

Right now I have a subscriber controller that creates a subscriber but that is not what I want to test. I also have a method in the controller that add 1 to the visit attribute on the Subscriber(I'll post the code) that is the method I want to test but I'm not sure how? I'm new to rails and Rspec so I'm having trouble grasping the concepts. I'll post my test and controller for clarity.
CONTROLLER:
def search
#subscriber = Subscriber.new
end
def visit
#subscriber = Subscriber.find_by_phone_number(params[:phone_number])
if #subscriber
#subscriber.visit =+ 1
#subscriber.save
flash[:notice] = "thanks"
redirect_to subscribers_search_path(:subscriber)
else
render "search"
end
end
TEST
it "adds 1 to the visit attribute" do
sign_in(user)
subscriber = FactoryGirl.create(:subscriber)
visits_before = subscriber.visit
post :create, phone_number: subscriber.phone_number
subscriber.reload
expect(subscriber.visit).to eq(visits_before)
end
ERROR MESSAGE:
As you can see that is the method I want to test. The current test in place does not work but I thought it might help to show what I'm thinking. Hopefully this is enough info, let me know if you want to see anything else?
I think you could do something like this:
it 'adds 1 to the visit attribute' do
# I'm assuming you need this, and you are creating the user before
sign_in(user)
# I'm assuming your factory is correct
subscriber = FactoryGirl.create(:subscriber)
visits_before = subscriber.visit
post :create, subscriber: { phone_number: subscriber.phone_number }
subscriber.reload
expect(subscriber.visit).to eq(visits_before)
end
Since you are checking subscriber.visits you should change Subscriber to subscriber:
expect { post :create, :subscriber => subscriber }.to change(subscriber, :visit).by(1)
visits is a method of an instance, not a class method.
I think you're testing the wrong method. You've already stated that your create action works, so no need to test it here. Unit tests are all about isolating the method under test.
Your test as it is written is testing that post :create does something. If you want to test that your visit method does something, you'd need to do something like this:
describe "#GET visit" do
before { allow(Subscriber).to receive(:find).and_return(subscriber) }
let(:subscriber) { FactoryGirl.create(:subscriber) }
it "adds one to the visit attribute" do
sign_in(user)
expect { get :visit }.to change(subscriber, :visit).by(1)
end
end

How to call the create action from the controller in RSpec

I have a controller create action that creates a new blog post, and runs an additional method if the post saves successfully.
I have a separate factory girl file with the params for the post I want to make. FactoryGirl.create calls the ruby create method, not the create action in my controller.
How can I call the create action from the controller in my RSpec? And how would I send it the params in my factory girl factories.rb file?
posts_controller.rb
def create
#post = Post.new(params[:post])
if #post.save
#post.my_special_method
redirect_to root_path
else
redirect_to new_path
end
end
spec/requests/post_pages_spec.rb
it "should successfully run my special method" do
#post = FactoryGirl.create(:post)
#post.user.different_models.count.should == 1
end
post.rb
def my_special_method
user = self.user
special_post = Post.where("group_id IN (?) AND user_id IN (?)", 1, user.id)
if special_post.count == 10
DifferentModel.create(user_id: user.id, foo_id: foobar.id)
end
end
end
Request specs are integration tests, using something like Capybara to visit pages as a user might and perform actions. You wouldn't test a create action from a request spec at all. You'd visit the new item path, fill in the form, hit the Submit button, and then confirm that an object was created. Take a look at the Railscast on request specs for a great example.
If you want to test the create action, use a controller spec. Incorporating FactoryGirl, that would look like this:
it "creates a post" do
post_attributes = FactoryGirl.attributes_for(:post)
post :create, post: post_attributes
response.should redirect_to(root_path)
Post.last.some_attribute.should == post_attributes[:some_attribute]
# more lines like above, or just remove `:id` from
# `Post.last.attributes` and compare the hashes.
end
it "displays new on create failure" do
post :create, post: { some_attribute: "some value that doesn't save" }
response.should redirect_to(new_post_path)
flash[:error].should include("some error message")
end
These are the only tests you really need related to creation. In your specific example, I'd add a third test (again, controller test) to ensure that the appropriate DifferentModel record is created.

Error Testing Create Action

I am trying to use factory girl to test my create action in rails. I keep getting:
"Event.count" didn't change by 1.
<2> expected but was
<1>.
When I run my tests. I don't believe I've done anything to change my tests or the controller:
My test looks like this:
test "should get create" do
assert_difference('Event.count') do
post :create, FactoryGirl.attributes_for(:event)
end
assert_not_nil assigns(:event)
assert_response :success
end
I've also tried using FactoryGirl.build(:event) as well. I've used that previously, and I think that is the right the method to be using from what I've read.
Here is my factory:
factory :event do
name 'First Event'
street '123 street'
city 'Chicago'
state 'IL'
date '1/12/2012'
end
Here is my controller action
def create
#event = Event.new(params[:event])
#event.save
end
Any idea what I'm doing wrong?
In order to scope out your error, try throwing a debugger in your action :
def create
#event = Event.new(params[:event])
debugger
#event.save
end
See if you manally resolve an #event.save and what errors might prevent that. :D

How to assert response code in Rails controller test?

EDIT
Edited to change to a question about the test rather than code, as I see the application behaves correctly.
I'm writing a Rails 3 app which is purely a RESTful web service (i.e. no views). I have a User model, where the username is unqiue
class User < ActiveRecord::Base
validates_uniqueness_of :username
end
In my controller, I have the following code to handle a new user being created:
def create
#user = User.new(ActiveSupport::JSON.decode(request.raw_post))
if #user.save
puts "Added user #{#user.username}"
format.json { render :json => "" }
else
puts "Failed to add user: #{#user.errors.to_json}"
render json: #user.errors, status: :unprocessable_entity
end
end
I then have a functional test which creates a user with the same username as an existing user:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_response :unprocessable_entity
end
When I run the test, the controller outputs "Failed to add user: {"username":["has already been taken"]}" as expected, but the test fails:
Expected response to be a <:unprocessable_entity>, but was <200>
However, with curl I get the response I expect:
curl -i -X POST -d '{"username": "james", "email": "test#test.com" }'
HTTP/1.1 422
{"username":["has already been taken"]}
So where am I going wrong with the assertion in the test?
You probably should use respond_with. It will take care of lots of REST logic, including setting status codes.
Also, Rails may be overkill for an application that's just a REST service. You might want to consider Sinatra instead.
The reason was that I'd switched to using RackTest to allow me to post JSON in the body (i.e. not as a form parameter.) As a result, I should have been making assertions on last_response (the RackTest MockResponse used in the post call) instead of using assert_response:
test "should not create user with duplicate username" do
#jim = users(:jim)
post '/users.json', #jim.to_json, "CONTENT_TYPE" => "application/json"
assert_status :unprocessable_entity
end
def assert_status(expected_status)
assert_equal last_response.status, Rack::Utils.status_code(expected_status)
end
I am quite surprised assert_response :success passes when no methods have been called which yield a value for #response. If that assertion failed, or threw an exception, it would have been easier to track down my bug, but c'est la vie!

Resources