Rails API ActionController::RoutingError: No route matches [POST] "/goals" - ruby-on-rails

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

Related

How to do Integration Test create through other model

1) I have this model Job and the model institution
class Job < ApplicationRecord
belongs_to :institution
# others attibutes
end
2) This is my action create on JobsController - I need a institution to create a job. it is fine.
def create
build_job
save_job || render(:new, status: :unprocessable_entity)
end
3) This is the integration test that I created
I am not getting the success test
In params
-I also tried institution: #institution
-and also tried institution_id: #institution.id
require 'test_helper'
class JobActionsTest < ActionDispatch::IntegrationTest
setup do
#user = users(:standard)
sign_in #user
#institution = institutions(:standard)
end
test "can create a job through institution" do
get new_institution_job_path(#institution)
assert_response :success
assert_difference('Job.count') do
post jobs_path,
params: {job: {title: "Desenvolvedor", description: "Ruby",
requirements: "rspec and capybara",
start_date: Date.today,
end_date: Date.today + 5.days,
institution: #institution.id}}
end
assert_response :redirect
follow_redirect!
assert_response :success
end
end
4) And this is my console error
#Running:
E
Error:
JobActionsTest#test_can_create_a_job_through_institution:
ActiveRecord::RecordNotFound: Couldn't find Institution with 'id'=
app/controllers/jobs_controller.rb:74:in `job_scope'
app/controllers/jobs_controller.rb:52:in `build_job'
app/controllers/jobs_controller.rb:18:in `create'
test/integration/job_actions_test.rb:22:in `block (2 levels) in <class:JobActionsTest>'
test/integration/job_actions_test.rb:21:in `block in <class:JobActionsTest>'
bin/rails test test/integration/job_actions_test.rb:17
Start by nesting the jobs resource properly:
resources :institutions do
resources :jobs, only: [:new, :create]
end
# or to create the full suite
resources :institutions do
resources :jobs, shallow: true
end
This will give these routes:
Prefix Verb URI Pattern Controller#Action
institution_jobs POST /institutions/:institution_id/jobs(.:format) jobs#create
new_institution_job GET /institutions/:institution_id/jobs/new(.:format) jobs#new
...
Note that :institution_id is a now a part of URI pattern for the create route, and it will be available as params[:institution_id].
In your test you want to POST to /institutions/:institution_id/jobs:
require 'test_helper'
class JobActionsTest < ActionDispatch::IntegrationTest
setup do
#user = users(:standard)
sign_in #user
#institution = institutions(:standard)
end
# Use separate examples per route / case
test "can fill in form to create a new job" do
get new_institution_job_path(#institution)
assert_response :success
end
test "can create a job through institution" do
assert_difference ->{ #institution.jobs.count } do
post institution_jobs_path(#institution),
params: {
job: {
title: "Desenvolvedor",
description: "Ruby",
requirements: "rspec and capybara",
start_date: Date.today,
end_date: Date.today + 5.days
}
}
end
assert_redirected_to #institution.jobs.last
follow_redirect!
assert_response :success
end
end
Further you want to test that the job actually was created for the right institution. We do that by passing the lambda ->{ #institution.jobs.count }.
And that the users are redirected to the correct resource - not just somewhere - which is done with assert_redirected_to #institution.jobs.last.
It looks like that when you call at line 22
get new_institution_job_path(#institution)
the #institution object you have built in the setup block is not saved in the database.
The error you are receiving, ActiveRecord::RecordNotFound, says that it cannot be found an Institution with id nil.
You can easily check if I am guessing correctly by adding this assertion:
test "can create a job through institution" do
assert_not_nil(#institution.id) # or assert_not_equal(0, Institution.where(id: #institution.id).size)
get new_institution_job_path(#institution)
assert_response :success
#...
end
Make sure that your institutions(:standard) method looks like Institution.create!() and not like Institution.new or Institution.build

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

RSpec test failing for routes that accept id param

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.

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.

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