RSpec test failing for routes that accept id param - ruby-on-rails

I'm trying to test the ArticlesController in my Rails applications. All of the routes that do not accept params are passing. But all of the routes that expect an id param are failing.
Failures:
1) ArticlesController should find article by id
Failure/Error: get :info, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"info", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:26:in `block (2 levels) in <top (required)>'
2) ArticlesController should export folder
Failure/Error: get :export_folder, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"export_folder", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:56:in `block (2 levels) in <top (required)>'
3) ArticlesController should export an article by id
Failure/Error: get :export, id: #article[:id]
ActionController::UrlGenerationError:
No route matches {:action=>"export", :controller=>"articles", :id=>"60"}
# ./spec/controllers/articles_controller_spec.rb:50:in `block (2 levels) in <top (required)>'
config/routes.rb
get '/articles/list' => 'articles#list', defaults: { format: :html }
get '/articles/trendlist' => 'articles#trendlist', defaults: { format: :html }
get '/articles/show/:id' => 'articles#show', defaults: { format: :html }, as: :show_article
get '/articles/index'
get '/articles/info/:id' => 'articles#info', as: :article_info
get '/articles/export/:id' => 'articles#export', as: :export_article
get '/articles/view/:id' => 'articles#view'
get '/articles/favorite/:id' => 'articles#favorite'
get '/articles/trending' => 'articles#trending', defaults: { format: :json }
get '/articles/deleted' => 'articles#deleted', defaults: { format: :json }
get '/articles/csv/:id' => 'articles#csv'
get '/articles/export_folder/:id' => 'articles#export_folder', as: :export_folder
spec/controllers/articles_controller.rb
require 'spec_helper'
describe ArticlesController do
before(:all) do
Article.destroy_all
Comfy::Cms::Layout.destroy_all
Comfy::Cms::Site.destroy_all
site = FactoryGirl.create(:cms_site)
layout = FactoryGirl.create(:cms_layout, site_id: site[:id])
#article = FactoryGirl.create(:cms_page, layout_id: layout[:id], site_id: site[:id])
end
it 'should index articles' do
get :index
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
its 'should list articles' do
get :list
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
it 'should find article by id' do
get :info, id: #article[:id]
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should list deleted articles' do
get :deleted
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should list trending articles' do
get :trending
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'application/json; charset=utf-8')
end
it 'should update trending articles' do
get :trendlist
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
it 'should export an article by id' do
get :export, id: #article[:id]
expect(response.response_code).to eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/csv; charset=utf-8')
end
it 'should export folder' do
get :export_folder, id: #article[:id]
response.response_code.should eq(200)
expect(response.headers).to include( 'Content-Type' => 'text/html; charset=utf-8')
end
end
rake routes
Prefix Verb URI Pattern Controller#Action
tags GET /tags(.:format) tags#index
articles_list GET /articles/list(.:format) articles#list
articles_trendlist GET /articles/trendlist(.:format) articles#trendlist
articles GET /articles/show/:id(.:format) articles/articles#show
articles_index GET /articles/index(.:format) articles#index
GET /articles/info/:id(.:format) articles/articles#info
GET /articles/export/:id(.:format) articles/articles#export
GET /articles/view/:id(.:format) articles/articles#view
GET /articles/favorite/:id(.:format) articles/articles#favorite
articles_trending GET /articles/trending(.:format) articles#trending
articles_deleted GET /articles/deleted(.:format) articles#deleted
GET /articles/csv/:id(.:format) articles/articles#csv
GET /articles/export_folder/:id(.:format) articles/articles#export_folder
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
include ArticlesHelper
before_action :set_default_response_format, except: [:pdf, :show, :list, :trendlist, :export_folder]
def index
#articles = SearchArticlesCommand.new(params).execute
end
def deleted
#dlist = Article.deleted.map(&:article_id)
render :ids, status: :ok
end
def info
id = params[:id].to_i
#article = Article.published.find_by(id: id)
end
def list
#articles = Article.folder
render 'articles/list'
end
def favorite
...
render json: { result: true, is_liked: "#{is_liked}" }
end
def view
...
render json: { result: true }
end
def trending
load_trending_articles
end
def trendlist
load_trending_articles
render 'articles/trendlist'
end
def export
id = params[:id].to_i
#article = Article.published.find_by(id: id)
render pdf: #article.label.gsub(/\s/, '_'),
template: 'articles/export.pdf.erb',
dispostion: 'attachment',
locals: { paragraphs: #article.paragraphs, images: #article.images }

That is not really what namespace is used for. You can read up more on it here. Use resources instead and specify member for the one with id:
resources :articles, only: [] do
collection do
get :list
get :trendlist
get :trending
get :deleted
end
member do
get :info
get :export
get :view
get :favorite
get :csv
get :export_folder
end
end
get 'articles/index', to: 'articles#index'
get 'articles/show/:id', to: 'articles#show'

If you look at the output of rake routes you can see that Rails is looking for articles/articles#show etc. namespace is for creating routes which live in a namespace (duh) such as /admin/tools which would root to Admin::ToolsController.
You can instead use scope which adds a url prefix but not the namespace or resources:
resources :articles, only: [:show, :index] do
member do
get 'info'
get 'export' # Use /articles/1.format instead.
get 'view' # Do you need this? Code smell!
get 'favorite' # should be post - GET should never create or alter a resource.
get 'csv' # remove - use /articles/1.csv instead
get 'show' # /articles/show/3
end
collection do
get 'trending'
get 'deleted'
get 'trendlist'
get 'list' # Do you need this? Code smell!
get 'index' # /articles/index
end
end
I would also question why you actually need so many routes beyond the standard CRUD set.
Especially routes which are extremely in semantics like view and show and list and index.
I would use query parameters around a smaller set of routes as it reduces the amount of duplication on all levels.
/articles?filter=deleted => index
/articles?filter=trending
Rails also has a built in CSV mime type so you can do:
/articles/5.csv
class ProductsController < ApplicationController
def show
#article = Article.order(:name)
respond_to do |format|
format.html
format.csv { render text: #article.to_csv }
end
end
end
Using #article[:id] vs #article.id does work but its unidiomatic and very slightly slower since rails has to go through the [] method just to find the getter method. Its not a huge deal in this case but not great when you are dealing with a large number of objects.

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

Routes appear to exist in rake routes, unavailable when running spec

I cannot seem to get the following worked out.
Spec: (spec/api/power_ups_spec.rb)
describe Api::PowerUpsController, :type => :controller do
describe "GET power_ups" do
it "returns all power-ups" do
FactoryGirl.create :power_up, name: "Increase rate of take", description: "You gain points more quickly"
FactoryGirl.create :power_up, name: "Decrease rate of give", description: "You lose points more slowly"
get api_power_ups_path, {}, { "Accept" => "application/json" }
expect(response.status).to eq 200
body = JSON.parse(response.body)
power_up_names = body.map { |m| m["title"] }
expect(power_up_names).to match_array(["Increase rate of take",
"Decrease rate of give"])
end
end
end
Routes:
Rails.application.routes.draw do
namespace :api do
resources :power_ups, only: [:index]
end
end
Controller (app/controllers/api/power_up_controller.rb):
module Api
class PowerUpsController < ApplicationController
include ActionController::MimeResponds
respond_to :json
def index
respond_with PowerUp.all
end
end
end
Rake Routes:
Prefix Verb URI Pattern Controller#Action
api_power_ups GET /api/power_ups(.:format) api/power_ups#index
Error message on running spec:
Failure/Error: get api_power_ups_path, {}, { "Accept" => "application/json" }
ActionController::UrlGenerationError:
No route matches {:action=>"/api/power_ups", :controller=>"api/power_ups"}
get api_power_ups_path
this isn't how to use get in a controller spec.
In a controller-spec, you assume that the class-under-test in the controller... so you use get to call the actual method on the controller.
In this case the method is called index (ie, you have def index), so to activate the test you just call:
get :index
you use the path-helpers only when you are referring to other paths - eg where you get redirected to etc.

Rails API ActionController::RoutingError: No route matches [POST] "/goals"

I'm creating a simple API with Rails and my test for creating a Goal resource is failing due to a RoutingError -- but I'm not sure why.
I can see when I do rake routes that the POST /goals route exists.
In my my routes.rb, I've set up namespace :api to be path: '/', so I think my post request should be working.
Can anyone help me point out what I've done wrong?
Here's my routes.rb
Rails.application.routes.draw do
with_options except: [:new, :edit] do |list_only|
namespace :api, path: '/', constraints: { subdomain: 'api' } do
list_only.resources :goal
end
end
end
Here are my routes:
$ rake routes
Prefix Verb URI Pattern Controller#Action
api_goals GET /goals(.:format) api/goals#index {:subdomain=>"api"}
POST /goals(.:format) api/goals#create {:subdomain=>"api"}
api_goal GET /goals/:id(.:format) api/goals#show {:subdomain=>"api"}
PATCH /goals/:id(.:format) api/goals#update {:subdomain=>"api"}
PUT /goals/:id(.:format) api/goals#update {:subdomain=>"api"}
DELETE /goals/:id(.:format) api/goals#destroy {:subdomain=>"api"}
My goals_controller.rb
class API::GoalsController < ApplicationController
before_action :set_goal, only: [:show, :edit, :update, :destroy]
def index
goals = Goal.all
if is_complete = params[:is_complete]
goals = goals.where(is_complete: is_complete)
end
render json: goals, status: 200
end
def show
goal = Goal.find(params[:id])
render json: goal, status: 200
end
def create
goal = Goal.new(goal_params)
if goal.save
# render nothing: true, status: 204, location: goal
head 204, location: goal
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_goal
#goal = Goal.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def goal_params
params.require(:goal).permit(:description, :motivation, :completion_date, :is_complete)
end
end
Here's the test:
require 'test_helper'
class CreatingGoalsTest < ActionDispatch::IntegrationTest
test 'creates goals' do
post '/goals',
{
description: "string",
motivation: "another string",
completion_date: "01/01/2014",
is_complete: true
}.to_json,
{ 'Accept' => Mime::JSON, 'Content-Type' => Mime::JSON.to_s }
assert_equal 201, response.status
assert_equal Mime::JSON, response.content_type
goal = json(response.body)
assert_equal goal_url(goal[:id]), response.location
end
end
Here's the error:
$ rake test:integration
Run options: --seed 27226
# Running:
E....
Finished in 0.102621s, 48.7230 runs/s, 116.9351 assertions/s.
1) Error:
CreatingGoalsTest#test_creates_goals:
ActionController::RoutingError: No route matches [POST] "/goals"
test/integration/creating_goals_test.rb:5:in `block in <class:CreatingGoalsTest>'
Thank you!
This can happen when the subdomain is not set in the test as rails is expecting it, you can override the host that is provided by adding the following:
def setup
host! 'api.example.com'
end
to your test suite.
class CreatingGoalsTest < ActionDispatch::IntegrationTest
def setup
host! 'api.example.com'
end
test 'creates goals' do
post '/goals',
{
description: "string",
motivation: "another string",
completion_date: "01/01/2014",
is_complete: true
}.to_json,
{ 'Accept' => Mime::JSON, 'Content-Type' => Mime::JSON.to_s }
assert_equal 201, response.status
assert_equal Mime::JSON, response.content_type
goal = json(response.body)
assert_equal goal_url(goal[:id]), response.location
end
end
Further resources on subdomains and testing in rails: Testing in Rails with Subdomains

Rspec 2.10.1, Rails 3.2.13 - ActionController::RoutingError: No route matches

I'm writing an rspec test that sends a JSON via POST to a member route end point. But I'm getting a "no route matches" error when I do so. I'm not sure if there's something I need to add because this endpoint is a member route, or I'm just missing some HTTP request headers because I'm sending a JSON. Please help.
This is what I have:
Spec:
describe "#endpoint" do
context "type 1" do
before(:each) do
post :create, #params.merge(:abc => {:first_user_id => #user1.id, :second_user_id => #user2.id})
#mashup = assigns(:mashup)
end
it "should post the results successfully" do
units = [...]
users = [...]
params = #params.merge(:mashup_outcome => {:status => "success", :assetName => "MashupAssetName", :winningUserId => #user1.id}, :mashup_id => #mashup.id, :version_number => 1, :user_id => #user1.id, :id => #mashup.id ,:users => :users).to_json
#request.env["CONTENT_TYPE"] = "application/json"
#had to have a param key for my params below in order to bypass the NoMethodError
#In actual request body, it's just a JSON
post :over, :mashup => params
#mashups.in_progress.should be_false
end
end
context "type 2" do
before(:each) do
...
end
it "should post the results correctly" do
...
end
end
Routes:
namespace :mashup do
resources :mashups do
member do
post :endpoint
end
end
Controller:
def endpoint
if #mashup.complete_mashup(params)
render :json => api_success(#mashup.dpoints)
else
render :json => api_error({})
end
end
Error:
Failure/Error: post :endpoint, :mashup => params
ActionController::RoutingError:
No route matches {:mashup => "{...<JSON>...}", :controller => "api/mashup/mashups", :action => "endpoint"}

gem versionist(path strategy) test rspec

There is a method "sign_up" in controller
# controller/v3/users_controller
# POST api/v3/users/sign_up
def sign_up    
user = User.new(params[:user])
if user.save && user.update_attribute(:channel, "user_#{user.id}")
render json: { Auth: { message: t(:sign_up_ok), user_id: user.id, channel: user.channel } }, status: 201
else
  render json: { errors: Oj.load(user.errors.to_json) }, status: 400
end
end
route.rb
api_version(module: 'V3', path: 'api/v3') do
resources :users, only: [:index, :show, :destroy] do
collection do
post 'sign_up'
post 'sign_in'
end
end
I am trying to test it:
require 'spec_helper'
describe V3::UsersController do
describe "POST 'sign_up'" do
it "should be successful" do
post '/api/v3/users/sign_up'
assert_response 200
end
end
end
Get an error:
V3::UsersController POST 'sign_up' should be successful
Failure/Error: post '/api/v3/users/sign_up'
ActionController::RoutingError:
No route matches {:controller=>"v3/users", :action=>"/api/v3/users/sign_up"}
# ./spec/controllers/v3/user_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
rake routes
sign_up_api_v3_users POST /api/v3/users/sign_up(.:format) V3/users#sign_up
Please, give me advice how to test this method?
ruby-1.9.3
rails-3.2.12
The action is not supposed to include the full path, just the name of the action.
post :sign_up
RSpec infers the rest of the path based on the controller your using. As you can see in the error message, it figured out the controller was v3/users, and it took your action, which is not a valid action.

Resources