Testing singular resource controller with RSpec - ruby-on-rails

I have defined a singular resource in my routes.rb which looks like this
Rails.application.routes.draw do
resource :dog, only: [:create], to: "dog#create", controller: "dog"
end
After that I've defined a controller with a create action like this
class DogController < ApplicationController
def create
render json: {}, status: :ok
end
end
And now I'm trying to test it out with RSpec like this
require "rails_helper"
describe DogController do
it "works" do
post :create, params: { foo: :bar }
end
end
This is throwing this error instead of passing:
ActionController::UrlGenerationError:
No route matches {:action=>"create", :controller=>"dog", :foo=>:bar}
What am I doing wrong?

Change your route to
resource :dog, only: [:create], :controller => "dog"
It is better to use plural controllers even if its a singular resource
http://edgeguides.rubyonrails.org/routing.html#singular-resources

Your create action is not taking in any parameter. It's just rendering json and returning a status code

Related

Rspec no route matches polymorphic

I am trying to test my polymorphic comments create action but I always get no route matches error in rspec.
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#comment = #commentable.comments.new(comment_params)
#comment.user_id = current_user.id
#comment.save
redirect_to :back, notice: "Your comment was successfully posted."
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
Rspec
describe "POST #create" do
context "with valid attributes" do
before do
#project = FactoryGirl.create(:project)
#comment_attr = FactoryGirl.build(:comment).attributes
end
it "creates a new comment" do
expect{
post :create, params: { project_id: #project, comment: #comment_attr }
}.to change(Comment, :count).by(1)
end
end
end
I am using this approach for testing create action in my another controllers and there is everything good, but here from some reason is throws error. I think my error is in line where I pass params to post create action but I do not see error.
UPDATE
resources :projects do
resources :comments, module: :projects
resources :tasks do
resources :comments, module: :tasks
end
end
UPDATE 2
Failure/Error: post :create, params: { project_id: #project,
commentable: #project, comment: #comment_attr }
ActionController::UrlGenerationError:
No route matches {:action=>"create", :comment=>{"id"=>nil, "commentable_type"=>nil, "commentable_id"=>nil, "user_id"=>nil,
"body"=>"MyText", "created_at"=>nil, "updated_at"=>nil,
"attachment"=>nil}, :commentable=>#, :controller=>"comments", :project_id=>#}
I think that your controller namespace does not match the routes you define. The controller is defined to not be nested (CommentsController) whereas the corresponding routes are nested and inside a module projects as well. Nesting the routes will have no influence on the controller ActionDispatch is looking for. But defining a module for the route will lead to rails expecting a controller inside the module namespace. In your case that would be Projects::CommentsController and Tasks::CommentsController. Please see "2.6 Controller Namespaces and Routing" of "Rails Routing from the Outside In" for further details.
I added your routes to a brand new rails project and ran rails routes. The output is:
Prefix Verb URI Pattern Controller#Action
project_comments GET /projects/:project_id/comments(.:format) projects/comments#index
POST /projects/:project_id/comments(.:format) projects/comments#create
...
You can therefor either remove the module definition from your routes
resources :projects do
resources :comments
resources :tasks do
resources :comments
end
end
or nest the controllers inside in a project/task namespace. Given that you want polymorphism for your comments, I would advise using the first option.

Routing Error uninitialized constant rails

I'm getting this error.When I want to run te server on localhost:3000/api/v1/songs.json
Routing Error
uninitialized constant API::V1::SongsController.
Thats my routes.rb file:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :songs, only: [:index, :create, :update, :destroy]
end
end # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Routes
Routes match in priority from top to bottom
Helper HTTP Verb Path Controller#Action
Path / Url
Path Match
api_v1_songs_path GET /api/v1/songs(.:format)
api/v1/songs#index
POST /api/v1/songs(.:format)
api/v1/songs#create
api_v1_song_path PATCH /api/v1/songs/:id(.:format)
api/v1/songs#update
PUT /api/v1/songs/:id(.:format)
api/v1/songs#update
DELETE /api/v1/songs/:id(.:format)
api/v1/songs#destroy
and thats my SongsController:
class Api::V1::SongsController < Api::V1::BaseController
def index
respond_with Song.all
end
def create
respond_with :api, :v1, Song.create(song_params)
end
def destroy
respond_with Song.destroy(params[:id])
end
def update
song = Song.find(params["id"])
song.update_attributes(song_params)
respond_with song, json: song
end
private
def song_params
params.require(:song).permit(:id, :name, :singer_name, :genre, :updated_at, :tag)
end
end
I'm going to copy how we have this running, cause I cannot see a direct difference between your code and our code....
routes.rb
constraints subdomain: Settings.subdomains.api do # you can ignore this
namespace :api do
namespace :v1 do
resources :maps, only: [] do
collection do
get :world_map
end
end
end
end
maps_controller.rb
module Api
module V1
class MapsController < BaseController
def world_map; end
end
end
end
routes output
world_map_api_v1_maps GET /v1/maps/world_map(.:format) api/v1/maps#world_map {:subdomain=>"api"}
The spelling is really crucial from what I can see.
The directort structure for us:
app/controllers/api/v1/maps_controller.rb
So check those points and this should work because it's standard Rails magic.

Rspec controller action not found

test is failing becasue it says the action does not exist, when it clearly does. Is it becasue it is a nested route? Any thoughts?
Update:
I moved resources :orders outside of the nested route and tests passed. So it has something to do with it being nested.
OrderController
def index
if current_printer
#orders = Order.all
#printer = Printer.find(params[:printer_id])
end
if current_user
#orders = Order.where(user_id: params[:user_id])
end
end
OrdersController Spec
require 'rails_helper'
RSpec.describe OrdersController, :type => :controller do
describe "unauthorized user" do
before :each do
# This simulates an anonymous user
login_with_user nil
binding.pry
end
it "should be redirected back to new user session" do
get :index
expect( response ).to redirect_to( new_user_session_path )
end
end
end
Routes
resources :users, only: [:index, :show] do
resources :orders
end
Error
Failures:
1) OrdersController unauthorized user should be redirected back to new user session
Failure/Error: get :index
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"orders"}
When testing controllers that have nested routes you must pass in a hash of the url params.
for example my routes looked like this
user_orders GET /users/:user_id/orders(.:format) orders#index
so in my test I passed in a hash with user_id
get :index, { user_id: 1 }
Tests passing :)

Rspec fails with ActionController::UrlGenerationError

Rspec fails with ActionController::UrlGenerationError with a URL I would think is valid. I've tried messing with the params of Rspec request, as well as fiddled with the routes.rb, but I'm still missing something.
The weird thing is, it works 100% as expected when testing locally with curl.
Error:
Failure/Error: get :index, {username: #user.username}
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"api/v1/users/devices", :username=>"isac_mayer"}
Relevant code:
spec/api/v1/users/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe Api::V1::Users::DevicesController, type: :controller do
before do
#user = FactoryGirl::create :user
#device = FactoryGirl::create :device
#user.devices << #device
#user.save!
end
describe "GET" do
it "should GET a list of devices of a specific user" do
get :index, {username: #user.username} # <= Fails here, regardless of params. (Using FriendlyId by the way)
# expect..
end
end
end
app/controllers/api/v1/users/devices_controller.rb
class Api::V1::Users::DevicesController < Api::ApiController
respond_to :json
before_action :authenticate, :check_user_approved_developer
def index
respond_with #user.devices.select(:id, :name)
end
end
config/routes.rb
namespace :api, path: '', constraints: {subdomain: 'api'}, defaults: {format: 'json'} do
namespace :v1 do
resources :checkins, only: [:create]
resources :users do
resources :approvals, only: [:create], module: :users
resources :devices, only: [:index, :show], module: :users
end
end
end
Relevant line from rake routes
api_v1_user_devices GET /v1/users/:user_id/devices(.:format) api/v1/users/devices#index {:format=>"json", :subdomain=>"api"}
The index action requires a :user_id parameter, but you haven't supplied one in the params hash. Try:
get :index, user_id: #user.id
The error message is a bit confusing, because you aren't actually supplying a URL; instead you are calling the #get method on the test controller, and passing it a list of arguments, the first one is the action (:index), and the second is the params hash.
Controller specs are unit tests for controller actions, and they expect that the request parameters are correctly specified. Routing is not the responsibility of the controller; if you want to verify that a particular URL is routed to the right controller action (since as you mention, you are using friendly-id), you may want to consider a routing spec.

"resources :post, except: :new" makes Rails think the route /posts/new would point to a post ID "new"

I have the following route:
resources :success_criteria, except: :new
The following spec fails:
describe SuccessCriteriaController do
describe 'routing' do
it 'routes to #new' do
expect(get('/success_criteria/new')).to_not be_routable
end
end
end
Failure message:
Failure/Error: expect(get('/posts/new')).to_not be_routable
expected {:get=>"/posts/new"} not to be routable, but it routes to {:controller=>"posts", :action=>"show", :id=>"new"}
The controller looks like this:
class SuccessCriteriaController < InheritedResources::Base
load_and_authorize_resource
end
Why does Rails think that posts/new would point to a post with the ID new? That's not right, is it? Does it maybe have to do with InheritedResources?
I believe that if you don't add a constraint to your show route saying that it will only accept digits, everything you put after posts are mapped to be an id to that route.
That means that if your try to access posts/something it would probably throw an ActiveRecord error showing that it couldn't find the Post with id=something.
To add a constraint, add constraints like this:
resources :success_criteria, except: :new, constraints: { id: /\d+/ }

Resources