Rails post from RSpec throws "no route matches" - ruby-on-rails

I have route definition
post '/session/create' => 'sessions#create', as: 'create_session'
controller
class SessionsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token, :only => :create
def create
# do some staff
end
end
If I send post request using RestConsole - it works like it should, but if I try to send post from RSpec test like below - it throws exception.
require 'spec_helper'
describe SessionsController do
let(:user_data_to_post) { FactoryGirl.attributes_for :user }
let(:user) do
mock_model User
end
describe 'create new session should be successful' do
before do
post(create_session_path, user_data_to_post.to_json,
{'HTTP_CONTENT_TYPE' => 'application/json'})
end
it "should call search_by_email of User model" do
User.should_recive(:search_by_email).with(user_data_to_post[:email])
end
end
end
error message:
Failure/Error: post(create_session_path, user_data_to_post.to_json,
ActionController::UrlGenerationError:
No route matches {:HTTP_CONTENT_TYPE=>"application/json", :controller=>"sessions", :action=>"/session/create"}
# ./spec/controllers/sessions_controller_spec.rb:13:in `block (3 levels) in <top (required)>'

Try:
post :create, user_data_to_post, format: :json
Firstly, post method expects the name of the action on the controller you passed to initial describe. Since you passed the path instead, it was treating the whole path as an action name, which obviously was not routed.
Secondly, post method signature is sth in line post(action_name, raw_post_data(assigned only if string), params=nil, session=nil, flash=nil) (in fact it is post(action_name, *args) but this is how those args are being processed. Sine you wanted to modify headers, you need to do this directly on a request:
#request['HTTP_CONTENT_TYPE'] = 'application/json'
or to pass format: :json along with params

Related

How to test a customized not_found route in rails

I have the following situation:
EDITED
In my routes.rb
namespace :api, defaults: { format: :json } do
namespace :v1 do
# the definitions of other routes of my api
# ...
match '*path', to: 'unmatch_route#not_found', via: :all
end
end
EDITED
My controller:
class Api::V1::UnmatchRouteController < Api::V1::ApiController
def not_found
respond_to do |format|
format.json { render json: { error: 'not_found' }, status: 404 }
end
end
end
My test is as shown:
require 'rails_helper'
RSpec.describe Api::V1::UnmatchRouteController, type: :controller do
describe 'get response from unmatched route' do
before do
get :not_found, format: :json
end
it 'responds with 404 status' do
expect(response.status).to eq(404)
end
it 'check the json response' do
expect(response.body).to eq('{"error": "not_found"}')
end
end
end
It seems right to me, however I got the same error for both it statments:
1) Api::V1::UnmatchRouteController get response from unmatched route responds with 404 status
Failure/Error: get :not_found, format: :json
ActionController::UrlGenerationError:
No route matches {:action=>"not_found", :controller=>"api/v1/unmatch_route", :format=>:json}
# /home/hohenheim/.rvm/gems/ruby-2.3.1#dpms-kaefer/gems/gon-6.1.0/lib/gon/spec_helpers.rb:15:in `process'
# ./spec/controllers/api/v1/unmatch_route_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
EDITED
The purpose with this route is be trigged when there's no other route possible in my api, with a custom json 404 response. This route and controller is working as expected right now, when we access routes like: /api/v1/foo or /api/v1/bar
How can I write the tests properly?
Additional info: Rails 4.2.6, Rspec 3.5.4
If you try to write routes spec, it won't work too and it will return something strange.
Failure/Error:
expect(get("/unmatch")).
to route_to("unmatch_route#not_found")
The recognized options <{"controller"=>"unmatch_route", "action"=>"not_found", "path"=>"unmatch"}> did not match <{"controller"=>"unmatch_route", "action"=>"not_found"}>, difference:.
--- expected
+++ actual
## -1 +1 ##
-{"controller"=>"unmatch_route", "action"=>"not_found"}
+{"controller"=>"unmatch_route", "action"=>"not_found", "path"=>"unmatch"}
Beside action not_found, it returned path => unmatch that maybe why controller spec didn't work as expected. Thus instead of controller test you can use request test as below.
require 'rails_helper'
RSpec.describe "get response from unmatched route", :type => :request do
before do
get '/not_found', format: :json
end
it 'responds with 404 status' do
expect(response.status).to eq(404)
end
it 'check the json response' do
expect(response.body).to eq('{"error": "not_found"}')
end
end
Take a look at this link:
https://apidock.com/rails/ActionDispatch/Routing/Mapper/Base/match
It says:
Note that :controller, :action and :id are interpreted as url query parameters and thus available through params in an action.
match ":controller/:action/:id"
Your route is:
match '*path', to: 'unmatch_route#not_found', via: :all
So your test is trying to find a route with :action=>"not_found" inside :controller=>"api/v1/unmatch_route". But your routes.rb does not have this route.
try something like this:
match 'unmatch_route/not_found', to: 'unmatch_route#not_found', via: :all
If you really need to use *path try this:
match '/:path/', :to => 'unmatch_route#not_found', :path=> /.*/, :as =>'not_found'
I also found myself wanting to test the response for API errors was rendering JSON, rather than writing a spec which simply rescued ActionController::RoutingError.
The following request spec worked for me, using Rails 6.0 & RSpec 3.9:
require 'rails_helper'
RSpec.describe '404 response for API endpoints' do
it 'renders an error in JSON' do
render_exceptions do
get '/api/v1/fictional-endpoint', headers: { 'Accept' => 'application/json' }
end
expect(response).to have_http_status(:not_found)
expect(response['Content-Type']).to include('application/json')
expect(json_response.fetch(:errors)).to include('Not found')
end
private
def json_response
JSON.parse(response.body, symbolize_names: true)
end
def render_exceptions
env_config = Rails.application.env_config
original_show_exceptions = env_config['action_dispatch.show_exceptions']
original_show_detailed_exceptions = env_config['action_dispatch.show_detailed_exceptions']
env_config['action_dispatch.show_exceptions'] = true
env_config['action_dispatch.show_detailed_exceptions'] = false
yield
ensure
env_config['action_dispatch.show_exceptions'] = original_show_exceptions
env_config['action_dispatch.show_detailed_exceptions'] = original_show_detailed_exceptions
end
end
References:
How to have Rails request specs handling errors like production
Comment regarding Rails.application.env_config caching

get :index Rspec error, during unit test for Static Index controller page

I'm currently attempting my first unit test and I'm receiving the following errors
Failures:
1) StaticPagesController GET #index responds successfully with an HTTP 200 status code
Failure/Error: get :index
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# /Users/danielmayle/.rvm/gems/ruby-2.2.1/gems/devise-3.5.2/lib/devise/controllers/helpers.rb:112:in `authenticate_user!'
# ./spec/static_pages_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
2) StaticPagesController GET #index renders the index template
Failure/Error: get :index
NoMethodError:
undefined method `authenticate!' for nil:NilClass
# /Users/danielmayle/.rvm/gems/ruby-2.2.1/gems/devise-3.5.2/lib/devise/controllers/helpers.rb:112:in `authenticate_user!'
# ./spec/static_pages_controller_spec.rb:6:in `block (3 levels) in <top (required)>'
Here is my unit test code:
require 'rails_helper'
describe StaticPagesController, :type => :controller do
context "GET #index" do
before do
get :index
end
it "responds successfully with an HTTP 200 status code" do
expect(response).to be_success
expect(response).to have_http_status(200)
end
it "renders the index template" do
expect(response).to render_template("index")
end
end
end
And here is my static_controller.rb code:
class StaticPagesController < ApplicationController
def index
end
def landing_page
#featured_product = Product.first
end
def thank_you
#name = params[:name]
#email = params[:email]
#message = params[:message]
UserMailer.contact_form(#email, #name, #message).deliver_now
end
end
Why do are these errors coming up and how do I fix the problem? I've only been coding for a few months so any assistance would be much appreciated. Thanks :)
Update
Here is my application controller
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
rescue_from CanCan::AccessDenied do |exception|
redirect_to main_app.root_url, :alert => exception.message
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) << :username
end
end
Here is my user_mailer code as well
class UserMailer < ActionMailer::Base
default from: "dmayle012#gmail.com"
def contact_form(email, name, message)
#message = message
mail(:from => email,
:to => 'dmayle012#gmail.com',
:subject => "New ActionMail Message from #{name}")
end
end
Your problem is this line:
before_action :authenticate_user!
This makes devise to check authorisation for current_user, which is nil in your test. There are two ways to fix it depending on what your requirements are.
Firstly, if you want any internet user to be able to view your static pages without login, add:
skip_before_action :authenticate_user!
in your StaticPagesController. This will tell devise that you don't require current_user to be allowed to view the page (not really - it doesn't tell devise anything, it just not asking it to authorise user).
Second option - you need user to be logged in to view those pages. In this case you need to create fake session before you start your test using some helper methods provided by devise. This is very easy and well documented process, you can find the steps here: https://github.com/plataformatec/devise/wiki/How-To:-Test-controllers-with-Rails-3-and-4-(and-RSpec)#controller-specs. Not adding those steps in the answer as I believe you will go with first case (since every page requires some pages available without the session, at least for the login page).
Devise has some helper methods specifically for this. Here is how to setup your controller spec to get past your undefined method authenticate_user! for nil error
First you need to include them in your rails_helper.rb like this
# spec/rails_helper.rb
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
end
And then you can use the helper like this in your static pages controller
# spec/controllers/static_pages_controller_spec.rb
describe StaticPagesController, :type => :controller do
context "GET #index" do
before :each do
user = FactoryGirl.create(:user, approved: true)
sign_in :user, user
get :index
end
...
end
end

Specs for controller inside a module (versionist)

I'm creating an API in Rails and I use versionist to handle versions. I want to test API controllers, but I'm unable to create a valid request.
My controller:
class Api::V1::ItemsController < Api::V1::BaseController
def index
render json:'anything'
end
end
My spec:
describe Api::V1::ItemsController do
describe "#create" do
it "shows items" do
get :index, format: :json
end
end
end
routes.rb:
scope '/api' do
api_version(:module => "Api::V1", :path => {:value => "v1"}, :default => true) do
resources :items
end
end
The test doesn't check anything. Still, it raises an error:
Failure/Error: get :index, format: :json
ActionController::RoutingError:
No route matches {:format=>:json, :controller=>"api/v1/items", :action=>"index"}
I suppose that there is something wrong with the :controller key in the request, but I don't know how to fix it...
I was able to reproduce this locally. You need to move this to a request spec instead of a controller spec for this to work:
# spec/requests/api/v1/items_controller_spec.rb
describe Api::V1::ItemsController do
describe "#index" do
it "shows items" do
get '/api/v1/items.json'
# assert something
end
end
end
The versionist documentation says you need to do this when using the HTTP header or request parameter versioning strategies (https://github.com/bploetz/versionist#a-note-about-testing-when-using-the-http-header-or-request-parameter-strategies) but that's clearly not the case here. I'll file an issue to get this clarified in the documentation that you need to do this for all versioning strategies.

what is the wrong with this spec and controller code?

I'm trying to test an existing rails project with rspec. And I want to test a controller but getting an error which I can't solve :S
Here is the my spec code ;
require 'spec_helper'
describe BriefNotesController do
before(:all) do
#customer=Factory(:customer)
#project=Factory(:project_started, :owner => #customer)
end
context 'get :new' do
it 'should redirect to login page for not signed in users' do
get :new, :project_id => #project.id
response.should redirect_to("/kullanici-girisi")
end
it 'should be success and render new brief note page for project owner' do
sign_in #customer
get :new, :project_id => #project.id
response.should be_success
end
end
end
Here is the my controller code ;
class BriefNotesController < ApplicationController
before_filter :authenticate_user!
before_filter :find_project
def new
#brief_note = #project.brief_notes.new
end
def create
#brief_note = #project.brief_notes.build(params[:brief_note])
if #brief_note.save
redirect_to brief_project_path(#project)
else
render :action => :new
end
end
private
def find_project
#project = current_user.projects.find_by_cached_slug([params[:project_id]])
end
end
I think current_user.projects.find_by_cached_slug method don't work. So this is the error;
Failures:
1) BriefNotesController get :new should be success and render new brief note page for project owner
Failure/Error: get :new, :project_id => #project.id
NoMethodError:
undefined method `brief_notes' for nil:NilClass
# ./app/controllers/brief_notes_controller.rb:6:in `new'
# ./spec/controllers/brief_notes_controller_spec.rb:19:in `block (3 levels) in <top (required)>'
I can't say for certain without more information about your models, but a likely culprit is that you're passing #project.id as the request parameter, but you're doing the lookup by cached_slug. Try #project.to_param instead.
The error is coming from your find_project filter: find_by_cached_slug is returning a nil, which is assigned to #project, and that triggers the undefined method error when brief_notes is called on it (in the new action).
From your spec description I assume that it shouldn't even be executing the new code, and instead redirecting in authenticate_user!? I don't use devise myself (this is a devise method, right?) so I'm not sure of the specifics of that method, but I think that's where your problem is coming from.
I don't think the problem is your FactoryGirl syntax, which is deprecated but should still work.

No route matches using rspec on a get request

What am I forgetting?
routes:
get "/comingsoon" => "visitors#comingsoon"
resources :visitors
controller:
class VisitorsController < ApplicationController
def comingsoon
#new_visitor = Visitor.new
end
end
spec:
require 'spec_helper'
describe VisitorsController do
describe "GET /comingsoon" do
it "should be happy" do
get "/comingsoon"
response.should be_success
end
end
end
And here's the result:
✗ rspec spec/controllers/visitors_controller_spec.rb
F
Failures:
1) VisitorsController GET /comingsoon should be valid
Failure/Error: get "/comingsoon"
ActionController::RoutingError:
No route matches {:controller=>"visitors", :action=>"/comingsoon"}
# ./spec/controllers/visitors_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
Finished in 0.14226 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/visitors_controller_spec.rb:6 # VisitorsController GET /comingsoon should be valid
What am I forgetting?
In your spec file replace get "/comingsoon"
with get "comingsoon"
When you spec a controller with rspec the operand of the http verb (get, post, put, delete) is an action of the controller rather than a url.
Possibly daft suggestion, but you have a view right? Otherwise you have to tell your controller to render something.

Resources