Testing rails controller with rspec - ruby-on-rails

I'm new to rails and I'm trying to test a controller with rspec. My first test is when the show action is invoked, it should lookup a Category by url.
The problem is when I add the stubbing code, I get the following error:
undefined method `find' for #
my test looks like this:
require 'spec_helper'
describe CategoriesController do
describe "GET /category-name/" do
before(:each) do
#category = mock_model(Category)
Category.stub!(:find).with(:first, :conditions => ["url = :url", {:url => "category-name"}]).and_return(#category)
end
it "should find the category by url" do
controller.show
Category.should_receive(:find).with(:first, :conditions => ["url = :url", {:url => "category-name"}]).and_return(#category)
end
end
end

Your call to the request should be after any should_receive. It's a tense thing. So it kind of reads like this, "Category should receive something, when this happens". "This happens" refers to the request.
it "should find the category by url" do
Category.should_receive(:find).with...
get "show", { your params, if you're sending some in }
end
Also, you want to go the way of a request vs calling the controller method itself, for this particular test at least.
So
post "action_name"
get "action_name"
update "action_name"
delete "action_name"
instead of
controller.action_name

Related

Rspec controller test: undefined method 'orders_path'

Writing some controller tests, using render_views to check some partial rendering....
describe PromoCodeController do
render_views
describe "GET 'show" do
... a bunch of tests
it "renders 'used' partial when promo code has already been used" do
#promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
get 'show', :slug => #promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
which loads in the _used partial
<article>
<p><%= #promo.description.html_safe %></p>
<p>Sorry, it appears this promo code has already been used. Please try again or contact us directly.</p>
<%= link_to "View Order", orders_path(#order), class: "box-button-black", data: { bypass: true } %>
</article>
but breaks with:
undefined method `orders_path' for #<#<Class:0x007fd4069d06e8>:0x007fd401e3e518>
Any ideas on how to either
(a) ignore the Rails link, it's irrelevant to the test
(b) include something in the test to recognize that link
(c) stub it (last resort i think)
Everything I've tried so far doesn't get past the error.
EDIT:
orders_path was wrong, it should be order_path. After changing that I get:
ActionView::Template::Error:
No route matches {:controller=>"order", :action=>"show", :id=>nil}
So the partial is looking for #order. I tried setting it with controller.instance_variable_set(:#order, create(:order)), but in the partial it comes back as nil.
A quick test by adding<% #order = Order.last %> in the view partial passes green. How to pass the var #order into the _used partial is now the question.
Instead of manually setting the spec type you can set it based off file location
# spec_helper.rb
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
describe 'GET SHOW' do
run this in a before block
before do
controller.instance_variable_set(:#order, create(:order))
end
it "renders 'used' partial when promo code has already been used" do
promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
# check if #order variable is assigned in the controller
expect(assigns(:order).to eq order
get 'show', slug: promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
end
First off, I needed to change it to order_path, orders_path was wrong. Doh.
Than I needed to stub some methods to get around the error
ActionView::Template::Error:
No route matches {:controller=>"order", :action=>"show", :id=>nil}
Ultimately, stubbing the method assign_promo_to_users_order which assigns a complete order to the current_user did the trick:
it "renders 'used' partial when promo code has already been used" do
#promo_code = create(:promo_code)
#user.stub(:promo_used?).and_return(true)
User.any_instance.stub(:assign_promo_to_users_order).and_return(create(:order, :complete))
get 'show', :slug => #promo_code.slug
expect(response).to render_template(:partial => 'promo_code/_used')
end
Try adding the spec's type.
I believe that the action controller URL helpers are included into the spec type.
Try:
describe SomeController, type: :controller do

Test JSON response of a POST Rails

I am developing an exercise in Rails. I just have to create some users and let them login. When a user is created, I need to send a json respond (I know how to do that), but I don't know how to test using RSPEC that that response is correct. This is my attempt:
describe "with correct input" do
before { #user.save }
specify { expect(User.count).to eq(1) }
it "should respond json" do
post '/signup.json', :user => {:name => 'ppppp', :login => '1234567890'}
json = JSON.parse(response.body)
expect(json["user_name"]).to eq('ppppp')
expect(json["login_count"]).to eq('1234567890')
end
end
When I try this I obtain the error: NoMethodError:undefined method `post'
Thank you!!
Is this a controller spec? If yes, is the spec file placed under spec/controllers folder.
By default Rspec assumes the controller specs to be under this folder. If the file isn't in the controllers folder add :type => :controller to the example group.
Refer the Rspec documentation.
Hope this helps.

Mocking and stubbing in testing

I've recently learned how to stub in rspec and found that some benefits of it are we can decouple the code (eg. controller and model), more efficient test execution (eg. stubbing database call).
However I figured that if we stub, the code can be tightly tied to a particular implementation which therefore sacrifice the way we refactor the code later.
Example:
UsersController
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
User.create(name: params[:name])
end
end
Controller spec
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
By doing that didn't I just limit the implementation to only using User.create? So later if I change the code my test will fail even though the purpose of both code is the same which is to save the new user to database
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new
#user.name = params[:name]
#user.save!
end
end
Whereas if I test the controller without stubbing, I can create a real record and later check against the record in the database. As long as the controller is able to save the user Like so
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
post :create, :name => "abc"
user = User.first
expect(user.name).to eql("abc")
end
end
end
Really sorry if the codes don't look right or have errors, I didn't check the code but you get my point.
So my question is, can we mock/stub without having to be tied to a particular implementation? If so, would you please throw me an example in rspec
You should use mocking and stubbing to simulate services external to the code, which it uses, but you are not interested in them running in your test.
For example, say your code is using the twitter gem:
status = client.status(my_client)
In your test, you don't really want your code to go to twitter API and get your bogus client's status! Instead you stub that method:
expect(client).to receive(:status).with(my_client).and_return("this is my status!")
Now you can safely check your code, with deterministic, short running results!
This is one use case where stubs and mocks are useful, there are more. Of course, like any other tool, they may be abused, and cause pain later on.
Internally create calls save and new
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
So possibly your second test would cover both cases.
It is not straight forward to write tests which are implementation independent. That's why integration tests have a lot of value and are better suited than unit tests for testing the behavior of the application.
In the code you're presented, you're not exactly mocking or stubbing. Let's take a look at the first spec:
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect(User).to receive(:create)
post :create, :name => "abc"
end
end
end
Here, you're testing that User received the 'create' message. You're right that there's something wrong with this test because it's going to break if you change the implementation of the controllers 'create' action, which defeats the purpose of testing. Tests should be flexible to change and not a hinderance.
What you want to do is not test implementation, but side effects. What is the controller 'create' action supposed to do? It's supposed to create a user. Here's how I would test it
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it 'saves new user' do
expect { post :create, name: 'abc' }.to change(User, :count).by(1)
end
end
end
As for mocking and stubbing, I try to stay away from too much stubbing. I think it's super useful when you're trying to test conditionals. Here's an example:
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
flash[:success] = 'User created'
redirect_to root_path
else
flash[:error] = 'Something went wrong'
render 'new'
end
end
# /spec/controllers/users_controller_spec.rb
RSpec.describe UsersController, :type => :controller do
describe "POST 'create'" do
it "renders new if didn't save" do
User.any_instance.stub(:save).and_return(false)
post :create, name: 'abc'
expect(response).to render_template('new')
end
end
end
Here I'm stubbing out 'save' and returning 'false' so I can test what's supposed to happen if the user fails to save.
Also, the other answers were correct in saying that you want to stub out external services so you don't call on their API every time you're running your test suite.

Using mocha for controller in functional test with RSPEC

I'm doing some tests here using Rspec and I would like to assure that the controller is calling the log method in some actions. I'm also using mocha.
I would like something like this:
it "update action should redirect when model is valid" do
Tag.any_instance.stubs(:valid?).returns(true)
put :update, :id => Tag.first
controller.expects(:add_team_log).at_least_once
response.should redirect_to(edit_admin_tag_url(assigns[:tag]))
end
is there something to use as the 'controller' variable? I tried self, the controller class name...
I just got helped with this. For testing controllers, you'd nest your specs inside a describe which names the controller. (The spec should also be in the Controllers folder)
describe ArticlesController do
integrate_views
describe "GET index" do
...
it "update action should redirect when model is valid" do
...
controller.expects(:add_team_log).at_least_once
...
end
end
end
I think you want #controller instead of controller. Here's an example from my test suite:
it "delegates to the pricing web service" do
isbn = "an_isbn"
#controller.expects(:lookup)
.with(isbn, anything)
.returns({asin: "an_asin"})
get :results, isbn: isbn
assert_response :success
end

Rspec test for the existence of an action is not working

i am working in Rspec of ROR..
I am trying to test my controllers using RSpec.i am having a Users controller with functions like new , tags, etc..
i created a file under spec/users_controller_spec.rb
and added the test cases as.
require 'spec_helper'
describe UsersController do
integrate_views
it "should use UsersController" do
controller.should be_an_instance_of(UsersController)
end
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
it "should have the title" do
get 'new'
response.should have_tag("title", "First app" )
end
end
end
which gets pass.
But when i add a test case for tags ..
like
describe "GET 'tags'" do
it "should be successful" do
get 'tags'
response.should be_success
end
end
this results in an error as
F...
1)
'UsersController GET 'tags' should be successful' FAILED
expected success? to return true, got false
why it is coming like this ?? i am very new to ROR and cant find the reason of why i am getting this error..
How to make this pass .
Also i tried the Url
http://localhost:3000/users/tags which is running for me .. But on testing using $spec spec/ i am getting the error ..
Your test may be failing for any number of reasons. Does the route require an ID in the parameter hash? Is the controller action redirecting? Is the controller raising an error?
You'll need to look at the controller code /and/or routes.rb to discover the cause of the failure. Take note of before filters in the controller, which may not allow the action to be reachable at all.
You need to add custom routes that are not within the default 7 routes. Assuming you have resources :users within your routes you will need to modify it. I'm also assuming that your tags route is unique to individual users.
resources :users do
member do
# creates /users/:user_id/tags
get :tags
end
end
And in your RSpec test you would call it like
describe '#tags' do
user = create :user
get :tags, user_id: user.id
end
If the route is not to be unique per user the other option is a collection route, something like:
resources :users do
collection do
# creates /users/tags
get :tags
end
end

Resources