Writing specs for a json api the routes are defaulted to only accept json requests like so:
Rails.application.routes.draw do
namespace :api, default: { format: 'json' } do
namespace :v1 do
resources :users, only: [:create]
end
end
end
I keep getting the following error:
Failure/Error: post :create, json
ActionController::UrlGenerationError:
No route matches {:action=>"create", :company_name=>"Wilderman, Casper and Medhurst", :controller=>"api/v1/users", :email=>"lillie_prohaska#example.com", :password=>"difdcbum5q", :username=>"gielle"}
Traditionally to get around this error I have set the CONTENT_TYPE and the HTTP_ACCEPT on the request so that it will pass the json formatting requirement.
My specs are written like so:
describe Api::V1::UsersController do
before :each do
#request.env['HTTP_ACCEPT'] = 'application/json'
#request.env['CONTENT_TYPE'] = 'application/json'
end
describe "POST#create" do
context "with valid attirbutes" do
let(:json) { attributes_for(:user) }
it "creates a new user" do
expect{ post :create, json }.to change{ User.count }.by(1)
end
it "returns status code 200" do
post :create, json
expect(response.status).to be(200)
end
it "should contain an appropriate json response" do
post :create, json
user = User.last
json_response = {
"success" => true,
"id" => user.id.to_s,
"auth_token" => user.auth_token
}
expect(JSON.parse(response.body)).to eq (json_response)
end
end
end
end
I have also tried adding a hash with { format: 'json' } which has also failed me.
According to the comments on the accepted answer for the question below setting the request environment headers will no longer work with rspec 3:
Set Rspec default GET request format to JSON
How would this be achieved in rspec 3 in Rails 4.1.1?
Thanks!
Here is how I do it over here:
[:xml, :json].each do |format|
describe "with #{format} requests" do
let(:api_request) { { :format => format } }
describe "GET 'index'" do
before :each do
api_request.merge attributes_for(:user)
end
it 'returns HTTP success' do
get :index, api_request
expect(response).to be_success
end
end
end
Related
So I'm trying to test a post request in order to save some book details. The response comes as raw JSON due to its being stringified in the client using formData. So that I can format the response appropriately in the controller.
I cannot find any clear way to send raw JSON parameters as rails automatically coerce these parameters as a HASH. Any suggestion?
Rails 5.1.4
Rspec 3.7.2
books_spec.rb
# Test suite for POST /books
describe 'POST /books' do
# valid payload
let(:valid_attributes) do
# send stringify json payload
{
"title": "Learn Elm",
"description": "Some good",
"price": 10.20,
"released_on": Time.now,
"user_id": user.id,
"image": "example.jpg"
}.to_json
end
# no implicit conversion of ActiveSupport::HashWithIndifferentAccess into String
context 'when the request is valid' do
before { post '/books', params: valid_attributes, headers: headers }
it 'creates a books' do
expect(json['book']['title']).to eq('Learn Elm')
end
it 'returns status code 201' do
expect(response).to have_http_status(201)
end
end
context 'when the request is invalid' do
let(:valid_attributes) { { title: nil, description: nil }.to_json }
before { post '/books', params: valid_attributes, headers: headers }
it 'returns status code 422' do
expect(response).to have_http_status(422)
end
it 'returns a validation failure message' do
expect(response.body)
.to match(/Validation failed: Title can't be blank, Description can't be blank/)
end
end
end
books_controller.rb
# POST /books
def create
#book = current_user.books.create!(book_params)
render json: #book, status: :created
end
def book_params
binding.pry # request.params[:book] = HASH
parsed = JSON.parse(request.params[:book])
params = ActionController::Parameters.new(parsed)
params['image'] = request.params[:image]
params.permit(
:title,
:description,
:image,
:price,
:released_on,
:user
)
end
#you should try test the request first like this and if you see this helping you I will send you the 200 test
describe "#create" do
context "when the request format is not json" do
let(:error_message) do
{ "Invalid Request Format" => "Request format should be json" }
end
before do
post :create, headers: { "CONTENT_TYPE": "XML" }
end
it "should return a status of 400" do
expect(response).to have_http_status(400)
end
it "should return an invalid request format in the body" do
expect(json(response.body)).to eql error_message
end
end
end
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
I want to test my API with RSpec.
Api::V1::EventsController exists and has a create method. I use simple_token_authentication and pundit for security.
routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
# users/
resources :users, only: [:show, :update] do
# users/:id/events/
resources :events
end
end
end
end
Spec :
RSpec.describe Api::V1::EventsController, type: :controller do
describe 'events#create' do
before {
#user = User.create(email: 'm#m.fr', password: '12345678', password_confirmation: '12345678')
#user.reload
}
it 'should 401 if bad credentials' do
# Given the user
# When
post "/api/v1/users/#{#user.id}/events", {},
{
'x-user-email' => 'toto',
'x-user-token' => 'toto'
}
# Then
expect_status 401
end
end
end
And i get this error :
Failure/Error: post "/api/v1/users/#{#user.id}/events", {},
ActionController::UrlGenerationError:
No route matches {:action=>"/api/v1/users/1/events", :controller=>"api/v1/events"}
EDIT and answer :
I was confused and i was using rspec controller when i wanted to use rspec request.
Here's my working example :
RSpec.describe Api::V1::EventsController, type: :controller do
describe 'events#create' do
before {
#user = User.create(email: 'm#m.fr', password: '12345678', password_confirmation: '12345678')
#user.reload
}
it 'should 401 if bad credentials' do
# Given the user
# When
post "/api/v1/users/#{#user.id}/events", {}.to_json,
{
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'x-user-email' => 'toto',
'x-user-token' => 'toto'
}
# Then
expect_status 401
end
end
end
The post method in a controller spec takes an action name for the first parameter, not a path, so instead of:
post "/api/v1/users/#{#user.id}/events", #...
try:
post :create, #...
Controller specs are unit tests. If you want to test the entire stack, use a feature spec instead of a controller spec.
You don't need to do the request with the full path, because you already testing the Api::V1::EventsController.
So it will be much better to use special syntax for it:
post :create, nil, {
'x-user-email' => 'toto',
'x-user-token' => 'toto'
}
expect(response.response_code).to eq 401
If you want to test the route, you should do it in the routes specs:
# spec/routing/api_v1_events_routing_spec.rb
require "spec_helper"
RSpec.describe Api::V1::EventsController do
describe "routing" do
it "#create" do
expect(post: "/api/v1/users/1/events").to \
route_to(controller: "api/v1/events", action: "create", user_id: "1")
end
end
end
I've read through every similar question I could find and still can't figure out my problem.
# routes.rb
Rails.application.routes.draw do
resources :lists, only: [:index, :show, :create, :update, :destroy] do
resources :items, except: [:new]
end
end
# items_controller.rb (excerpt)
class ItemsController < ApplicationController
...
def create
#list = List.find(params[:list_id])
...
end
...
end
# items_controller_spec.rb (excerpt)
RSpec.describe ItemsController, type: :controller do
...
let!(:list) { List.create(title: "New List title") }
let(:valid_item_attributes) {
{ title: "Some Item Title", complete: false, list_id: list.id }
}
let!(:item) { list.items.create(valid_item_attributes) }
describe "POST #create" do
context "with valid params" do
it "creates a new item" do
expect {
post :create, { item: valid_item_attributes, format: :json }
}.to change(Item, :count).by(1)
end
end
end
...
end
And the RSpec error:
1) ItemsController POST #create with valid params creates a new item
Failure/Error: post :create, { item: valid_item_attributes, format: :json }
ActionController::UrlGenerationError:
No route matches {:action=>"create", :controller=>"items", :format=>:json, :item=>{:title=>"Some Item Title", :complete=>false, :list_id=>1}}
The output from rake routes:
list_items GET /lists/:list_id/items(.:format) items#index
POST /lists/:list_id/items(.:format) items#create
edit_list_item GET /lists/:list_id/items/:id/edit(.:format) items#edit
list_item GET /lists/:list_id/items/:id(.:format) items#show
PATCH /lists/:list_id/items/:id(.:format) items#update
PUT /lists/:list_id/items/:id(.:format) items#update
DELETE /lists/:list_id/items/:id(.:format) items#destroy
I can successfully create a new item in an existing list via curl which tells me that the route is ok, I must be doing something wrong in my test.
curl -i -X POST -H "Content-Type:application/json" -H "X-User-Email:admin#example.com" -H "X-Auth-xxx" -d '{ "item": { "title": "new item", "complete": "false"} }' http://localhost:3000/lists/5/items
I am really confused. My routes are setup correctly. A ItemsController#create method definitely exists. The rest of the tests in items_controller_spec.rb pass without issue.
Am I missing something obvious?
Here are the fixes I had to make to my tests (items_controller_spec.rb). I was not passing the correct hash to post create:.
describe "POST #create" do
context "with valid params" do
it "creates a new item" do
expect {
post :create, { list_id: list.id, item: valid_item_attributes, format: :json }
}.to change(Item, :count).by(1)
end
it "assigns a newly created item as #item" do
post :create, { list_id: list.id, item: valid_item_attributes, format: :json }
expect(assigns(:item)).to be_a(Item)
expect(assigns(:item)).to be_persisted
end
end # "with valid params"
context "with invalid params" do
it "assigns a newly created but unsaved item as #item" do
post :create, { list_id: list.id, item: invalid_item_attributes, format: :json }
expect(assigns(:item)).to be_a_new(Item)
end
it "returns unprocessable_entity status" do
put :create, { list_id: list.id, item: invalid_item_attributes, format: :json }
expect(response.status).to eq(422)
end
end # "with invalid params"
end # "POST #create"
I've received the same error and fixed it in a different way. I'm using Rails ~> 5.0.7.
ROUTES
This is the output from running rake routes CONTROLLER=bills:
Prefix Verb URI Pattern Controller#Action
download_site_bill GET /sites/:site_id/bills/:id/download(.:format) bills#download
site_bills GET /sites/:site_id/bills(.:format) bills#index
POST /sites/:site_id/bills(.:format) bills#create
new_site_bill GET /sites/:site_id/bills/new(.:format) bills#new
edit_site_bill GET /sites/:site_id/bills/:id/edit(.:format) bills#edit
site_bill GET /sites/:site_id/bills/:id(.:format) bills#show
PATCH /sites/:site_id/bills/:id(.:format) bills#update
PUT /sites/:site_id/bills/:id(.:format) bills#update
DELETE /sites/:site_id/bills/:id(.:format) bills#destroy
bills GET /bills(/page/:page)(.:format) bills#index
download_bill GET /bills/:id/download(.:format) bills#download
GET /bills(.:format) bills#index
POST /bills(.:format) bills#create
new_bill GET /bills/new(.:format) bills#new
edit_bill GET /bills/:id/edit(.:format) bills#edit
bill GET /bills/:id(.:format) bills#show
PATCH /bills/:id(.:format) bills#update
PUT /bills/:id(.:format) bills#update
DELETE /bills/:id(.:format) bills#destroy
REFERENCE CODE
# controllers/admin/bills_controller.rb (excerpt)
module Admin
class BillsController < ApplicationController
...
def edit
authorize(#bill)
end
end
end
# spec/controllers/admin/bills_controller_spec.rb (excerpt)
require 'rails_helper'
RSpec.describe Admin::BillsController, type: :controller do
...
context 'as an AdminUser' do
login_admin_user
it 'loads the bill edit page' do
request.host = 'admin.example.com'
get :edit, { id: bill }
expect(response.status).to eq(200)
end
end
end
end
# Error message
2) Admin::BillsController GET bills/:id/edit as a User redirects to the home page
Failure/Error: get :edit
ActionController::UrlGenerationError:
No route matches {:action=>"edit", :controller=>"admin/bills"}
POTENTIAL SOLUTION
I tried variations of this solution. It may work for others, but I got this error:
Failure/Error: require 'admin/bills_controller'
LoadError:
cannot load such file -- admin/bills_controller
SOLUTION
I tried Case 2 in this github issues and it worked. The changes I made to my code were to add this params: { use_route: 'admins/bills/', id: bill.id }. Below is the context of this addition:
context 'as an AdminUser' do
login_admin_user
it 'loads the bill edit page' do
request.host = 'admin.example.com'
get :edit, params: { use_route: 'admins/bills/', id: bill.id }
expect(response.status).to eq(200)
end
end
I am attempting to create a RSpec controller test for a namespaced controller, but rspec doesn't seem able to detect the nesting and generate the proper path for the post :create action.
This is my spec code:
# for: /app/controllers/admin/crm/report_adjustments_controller.rb
require 'spec_helper'
describe Admin::Crm::ReportAdjustmentsController do
render_views
before(:each) do
signin
end
describe "GET 'index'" do
it "returns http success" do
get :index
response.should be_success
end
end
describe "POST 'create'" do
it "creates with right parameters" do
expect {
post :create, report_adjustment: {distributor_id: #ole_distributor.id, amount: "30.0", date: Date.today }
}.to change(Crm::ReportAdjustment, :count).by(1)
response.should be_success
end
end
end
# routes.rb
namespace :admin do
namespace :crm do
resources :report_adjustments
end
end
For this code, the get :index works just fine, but when post :create is called, the following error is generated: undefined method 'crm_report_adjustment_url'
Why would RSpec be smart enough to figure things out with get :index, but not with post :create? How do I get RSpec to properly load the right route, which is admin_crm_report_adjustments_url?
Thanks in advance.
Try posting to the url instead:
post admin_crm_report_adjustments_url
# or
post "/admin/crm/report_adjustments"