RSpec Controller test, testing a post action - ruby-on-rails

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

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

How to write Rspec for basic Show, Create, Destroy, Edit methods in rails (with or without FactoryGirl)

I have a basic user_controller.rb file like this:
class UserController < ApplicationController
def new
#user = User.new
end
def index
#user = User.all
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
redirect_to action: 'index'
end
private
def user_params
params.require(:user).permit(:name, :key, :desc)
end
end
This is my (model) user.rb file:
class User < ApplicationRecord
validates :name, presence: true
validates :key, uniqueness: true, presence: true
validates :desc, presence: true
end
And created a factories.rb file (in the specs folder):
FactoryGirl.define do
factory :user do
name "TestUser"
key "TKey"
desc "TestDescription"
end
end
I tried several ways to make the specs work but I can't because of the confusing syntax.
The only one which worked was (for the 'C' in the CRUD operations, the below file is user_controller_specs.rb):
require 'rails_helper'
require 'factory_girl_rails'
RSpec.describe UserController, :type => :controller do
let(:temp) { FactoryGirl.build(:user) }
describe "POST create" do
it "should redirect back to the index page" do
post :create, :user => { :user => temp }
expect(get: user_url(subdomain: nil)).to route_to(controller: "user", action: "index")
end
end
end
I skimmed through several tutorials to find what should be the correct approach for CRUD operations but didn't got any simple to understand specs. I am trying to write these in the specs/controllers folder but these are giving errors. What should be the correct syntax to write the specs?
PS: I'am new to Ruby/Rails and trying to write test cases with Rspec and FactoryGirl. Any help is appreciated.
Edit:
Maybe I framed the question wrongly... I'm more interested in the syntax part. If I get to know an example how to write one, I'll be able to write others by changing some tiny bits of logic here and there.... Let's say I have a basic test case just to see whether updating a user details is not returning an error because of validations, how should I write it with (or without) Factory Girl gem?
It's a pretty broad question, but in any kind of test, you want test whatever use cases you have available to you. Example--are there different paths users might follow from hitting a specific controller action.
So you want your test to cover the basics. When you hit the create action, is a user actually created? If the relevant params are missing, is an error thrown? Use cases will drive your expectations.
With rspec controllers specifically, you'll use the appropriate verb and the name of the action, and pass it whatever parameters are necessary.
post :create, :user => { :user => temp }
That basically says, "do a post request to my create an action and pass it the parameters inside these curly braces."
After running that rspec gives you access to the response. You can always log the response after a controller request to help you debug the situation: p response.
You'll follow up each type of request with an expectation. The expectation should answer the question: "What did I expect hitting this action to do?" If you were, for instance, hitting the user update action and passed a param to change the user's age to 21, your expectation might be something like:
expect(user.age).to eq(21)
A great resource is the rspec documentation on relish. https://relishapp.com/rspec
"How to" do a broad general thing is a tough question to answer like this. My advice would be to try to actually test one, log the failure case, and post those logs in a new question and people on SO can help you work through testing a particular action you're struggling with.

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.

Ruby On Rails Function Testing :index with a polymorphic model

I have a polymorphic model called Address, I am trying to currently write some basic function tests for this model and controller. For the controller I am at a loss on how to go about this. For example I have another model called Patient, each Patient will have an address, so i have started writing the following function test, but i have no idea how to use "get" with a nested polymorphic resource. Now I was able to find some polymorphic test information on Fixtures here: http://api.rubyonrails.org/classes/Fixtures.html
but this will not help me test against the index. Any help is much appreciated im at a total and complete loss here.
FILE: test/functional/addresses_controller_test.rb
require 'test_helper'
class AddressesControllerTest < ActionController::TestCase
setup do
#address = addresses(:of_patient)
#patient = patients(:one)
activate_authlogic
end
test "patient addresses index without user" do
get :index <<<<<<<<<<<< what goes here????
assert_redirected_to :login
end
end
Assuming your controller is setup the way I think it might be:
def index
if #current_user
#addresses = #current_user.addresses.all
else
redirect_to login_path
end
end
Then the test will probably look like this:
test "patient addresses index without user" do
get :index, :patient_id => #patient.id
assert_redirected_to :login
end
test "patient addresses with user" do
#current_user = #patient
get :index, :patient_id => #patient.id
assert_response :success
end
The thing to keep in mind is that the index method needs the patient_id to process.

How to spec controller where model associations occur

I'm trying to spec the controller code:
# ClustersController
def create
# create new cluster
#cluster.user = current_user
# save code
end
I am using Rails 3 / RSpec 2 and I'm fairly new to the TDD flow. I basically want to make sure that the user attribute is assigned during the create action.
To begin with i don't think you should create, update an save the object. You can pass the user to the create method, like this:
Cluster.create(:user => current_user)
And to test this you can do:
describe ClusterController do
describe "POST create" do
it "creates a new cluster" do
lamda do
post :create
end.should change(Cluster, :count).by(1)
end
it "set the current user as the new cluster's user" do
user = mock()
Cluster.should_receive(:create).with(:user => user)
post :create
assign(:cluster).user.should == user
end
end
end
I think that will do.
Hope that help.

Resources