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
Related
I have a rails app with a cars model that takes the attribute of 'model'. I'm just trying to run a test to see if the index method in the cars controller will display all the cars.
I'm not sure how to go about this. The test currently passes, but it shouldn't as I currently have an empty index method. How could I test this and what do I need to add. I've tried to read documentation with no luck. thanks
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
context "test" do
it "displays all cars" do
get :index
end
end
end
with the current test file you have posted, your test should pass because you are doing a request to the index action and as long as there is an index action the test will pass.
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
context "test" do
it "displays all cars" do
get :index
end
end
end
However I see some people have recommended to use assigns, when using assigns to test you will need to assigns all the cars to an instance variable e.g. #cars = Cars.all or whatever records you need and also you should have your test data setup properly.
In your test you should also have some sort of list of cars to test against, e.g assuming you are using FactoryGirl/Bot:
let(:cars) { create_list :car, 3 }
You test file should than look something like this:
require 'rails_helper'
RSpec.describe CarsController, type: :controller do
let(:cars) { create_list :car, 3 }
context "test" do
it "displays all cars" do
get :index
expect(assigns(:cars)).to eq cars
end
end
end
Hope my answer helps.
You're looking to use assigns in your controller specs. Since you don't seem to have any code yet, the fastest way to show you an example is to just run rails g scaffold car. Assuming you have rspec-rails in your Gemfile, you should get something like this in spec/controllers/cars_controller_spec.rb:
require 'rails_helper'
# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec. Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.
RSpec.describe CarsController, type: :controller do
# This should return the minimal set of attributes required to create a valid
# Car. As you add validations to Car, be sure to
# adjust the attributes here as well.
let(:valid_attributes) {
skip("Add a hash of attributes valid for your model")
}
let(:invalid_attributes) {
skip("Add a hash of attributes invalid for your model")
}
# This should return the minimal set of values that should be in the session
# in order to pass any filters (e.g. authentication) defined in
# CarsController. Be sure to keep this updated too.
let(:valid_session) { {} }
describe "GET #index" do
it "assigns all cars as #cars" do
car = Car.create! valid_attributes
get :index, params: {}, session: valid_session
expect(assigns(:cars)).to eq([car])
end
end
describe "GET #show" do
it "assigns the requested car as #car" do
car = Car.create! valid_attributes
get :show, params: {id: car.to_param}, session: valid_session
expect(assigns(:car)).to eq(car)
end
end
describe "GET #new" do
it "assigns a new car as #car" do
get :new, params: {}, session: valid_session
expect(assigns(:car)).to be_a_new(Car)
end
end
describe "GET #edit" do
it "assigns the requested car as #car" do
car = Car.create! valid_attributes
get :edit, params: {id: car.to_param}, session: valid_session
expect(assigns(:car)).to eq(car)
end
end
describe "POST #create" do
context "with valid params" do
it "creates a new Car" do
expect {
post :create, params: {car: valid_attributes}, session: valid_session
}.to change(Car, :count).by(1)
end
it "assigns a newly created car as #car" do
post :create, params: {car: valid_attributes}, session: valid_session
expect(assigns(:car)).to be_a(Car)
expect(assigns(:car)).to be_persisted
end
it "redirects to the created car" do
post :create, params: {car: valid_attributes}, session: valid_session
expect(response).to redirect_to(Car.last)
end
end
context "with invalid params" do
it "assigns a newly created but unsaved car as #car" do
post :create, params: {car: invalid_attributes}, session: valid_session
expect(assigns(:car)).to be_a_new(Car)
end
it "re-renders the 'new' template" do
post :create, params: {car: invalid_attributes}, session: valid_session
expect(response).to render_template("new")
end
end
end
describe "PUT #update" do
context "with valid params" do
let(:new_attributes) {
skip("Add a hash of attributes valid for your model")
}
it "updates the requested car" do
car = Car.create! valid_attributes
put :update, params: {id: car.to_param, car: new_attributes}, session: valid_session
car.reload
skip("Add assertions for updated state")
end
it "assigns the requested car as #car" do
car = Car.create! valid_attributes
put :update, params: {id: car.to_param, car: valid_attributes}, session: valid_session
expect(assigns(:car)).to eq(car)
end
it "redirects to the car" do
car = Car.create! valid_attributes
put :update, params: {id: car.to_param, car: valid_attributes}, session: valid_session
expect(response).to redirect_to(car)
end
end
context "with invalid params" do
it "assigns the car as #car" do
car = Car.create! valid_attributes
put :update, params: {id: car.to_param, car: invalid_attributes}, session: valid_session
expect(assigns(:car)).to eq(car)
end
it "re-renders the 'edit' template" do
car = Car.create! valid_attributes
put :update, params: {id: car.to_param, car: invalid_attributes}, session: valid_session
expect(response).to render_template("edit")
end
end
end
describe "DELETE #destroy" do
it "destroys the requested car" do
car = Car.create! valid_attributes
expect {
delete :destroy, params: {id: car.to_param}, session: valid_session
}.to change(Car, :count).by(-1)
end
it "redirects to the cars list" do
car = Car.create! valid_attributes
delete :destroy, params: {id: car.to_param}, session: valid_session
expect(response).to redirect_to(cars_url)
end
end
end
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.
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
I've got some routes that look like this:
constraints ClientCodeConstraint.new do
get 'share/:client_code', to: redirect('/%{client_code}')
get 'share/:client_code/:id', to: redirect('/%{client_code}/%{id}')
get ':client_code', to: 'share#index', as: :shares
get ':client_code/:id', to: 'share#show', as: :share
get ':client_code/:document_id/more/:component_id', to: 'share#more', as: :more
end
My constraint looks like this:
class ClientCodeConstraint
def matches?(request)
users.each do |user|
return true if request.path_parameters[:client_code] == user.client_code
end
end
def users
#users ||= User.all
end
end
I've got tests like this but I'm having no luck:
context 'not logged in' do
let!(:user) { create(:user) }
let!(:document) { create(:document) }
specify 'renders the :index view' do
get :index
expect(response.code).to eq '302'
expect(response).to redirect_to("/#{assigns(:user).client_code}")
end
specify 'renders the :show view' do
get :show
expect(response).to redirect_to("/#{assigns(:user).client_code}/#{assigns(:document).id}")
end
end
How do I test with Rspec these redirects? I just get a bunch of these errors:
ActionController::RoutingError: No route matches {:controller=>"share"}
And the controller and routes of course exist.
UPDATE
Relevant rake routes output is here:
GET /share/:client_code(.:format) :controller#:action
GET /share/:client_code/:id(.:format) :controller#:action
shares GET /:client_code(.:format) share#index
share GET /:client_code/:id(.:format) share#show
more GET /:client_code/:document_id/more/:component_id(.:format) share#more
The tests pass like this:
specify 'renders the :index view' do
get :index, client_code: user.client_code
expect(response.status).to eq(200)
end
That feels though like I'm testing only that the response is correct. I want to test more thoroughly, i.e., it doesn't render an error message, etc.
I’m having a challenge write a RSpec controller test for a PATCH update, because the routing and edit uses a secure edit_id that my model generates, instead of the standard 1,2,3,4,5 (sequenced id) that Rails auto-generates. Basically, I’m not sure how to get my tests to lookup the request to be edited using this edit_id.
My test currently:
describe "PATCH edit/update" do
before :each do
#testrequest = FactoryGirl.build(:request, name: "James Dong")
end
it "located the requested #testrequest" do
patch :update, id: #testrequest.edit_id, request: FactoryGirl.attributes_for(:request)
assigns(:request).should eq(#testrequest)
end
describe "using valid data" do
it "updates the request" do
patch :update, #testrequest.name = "Larry Johnson"
#testrequest.reload
#testrequest.name.should eq("Larry Johnson")
end
end
FactoryGirl helper (I've tried both explicitly adding edit_id and not [i.e., relying on the model to create the edit_id itself], neither makes a difference) code:
FactoryGirl.define do
factory :request do |f|
f.name { Faker::Name.name }
f.email { Faker::Internet.email }
f.item { "random item" }
f.detail { "random text" }
f.edit_id { "random" }
end
end
Controller:
def update
#request = Request.find_by_edit_id(params[:edit_id])
if #request.update_attributes(request_params)
flash[:success] = "Your request has been updated! We'll respond within one business day."
redirect_to edit_request_path(#request.edit_id)
else
render 'edit'
end
end
Routing:
get 'edit/:edit_id', to: 'requests#edit', as: 'edit_request'
patch 'requests/:edit_id', to: 'requests#update', as: 'request'
Ok someone helped me figure this out, and I feel very silly. The "id" that you pass to the patch method can be any id, so instead of trying to set id: edit_it, I should use edit_it in the first place. I.e., the code that works:
before :each do
#testrequest = FactoryGirl.build(:request, name: "James Dong")
end
it "located the requested #testrequest" do
patch :update, edit_id: #testrequest.edit_id, request: FactoryGirl.attributes_for(:request)
assigns(:request).should eq(#testrequest)
end
describe "using valid data" do
it "updates the request" do
patch :update, edit_id: #testrequest.edit_id, request: FactoryGirl.attributes_for(:request, name: "Larry Johnson")
#testrequest.reload
#testrequest.name.should eq("Larry Johnson")
end
end