I have the following rspec test
context 'copy' do
let(:path) { '/sales_orders/copy' }
let(:params) { { id: sales_order.id } }
it 'copies passed in sales_order attrs' do
post path, params: params
expect(subject).to render_template('sales_orders/new')
expect(response).to have_http_status(200)
end
end
and the following in my routes
resources :sales_orders, except: %i[destroy]
The error is the following:
Failures:
1) SalesOrdersController copy copies passed in sales_order attrs
Failure/Error: expect(subject).to render_template('sales_orders/new')
expecting <"sales_orders/new"> but rendering with <[]>
# ./spec/requests/sales_order_spec.rb:435:in `block (3 levels) in <top (required)>'
Finished in 1 minute 0.96 seconds (files took 4.35 seconds to load)
1 example, 1 failure
resources :sales_orders only includes the standard routes (which you can find in the docs). However copy isn't a standard route. You can add that route and controller action like this:
# config/routes.rb
resources :sales_orders, except: %i[destroy] do
post :copy, on: :member
end
# app/controllers/sales_orders_controller.rb
class SalesOrdersController < ApplicationController
def copy
# ...
end
end
Also it looks like this action isn't doing anything, but it's just rendering a new form. In that case you might want to consider a GET request instead of POST.
Related
I was writing tests for my app using responders gem.
Here are my routes:
resources :sites do
resources :pages, shallow: true
end
My PagesController chunk of code:
def create
respond_with(#page = #site.pages.create(page_params))
end
def find_site
#site = current_user.sites.find(params[:site_id])
end
And tests that are failing:
sign_in_user
let(:user_2) { create(:user) }
let(:site) { create(:site, user: #user) }
let(:page) { create(:page, site: site, user: #user) }
describe 'POST #create' do
context 'with valid attributes' do
it 'associates new page with the site' do
expect { post :create, params: { page: attributes_for(:page), site_id: site } }.to change(site.pages, :count).by(1)
end
it 'redirects to show view' do
post :create, params: { page: attributes_for(:page), site_id: site }
expect(response).to redirect_to page_path(assigns(:page))
end
end
Errors are following:
1) PagesController POST #create with valid attributes associates new page with the site
Failure/Error: expect { post :create, params: { page: attributes_for(:page), site_id: site } }.to change(site.pages, :count).by(1)
expected #count to have changed by 1, but was changed by 0
# ./spec/controllers/pages_controller_spec.rb:37:in `block (4 levels) in <top (required)>'
2) PagesController POST #create with valid attributes redirects to show view
Failure/Error: expect(response).to redirect_to page_path(assigns(:page))
ActionController::UrlGenerationError:
No route matches {:action=>"show", :controller=>"pages", :id=>nil}, missing required keys: [:id]
# ./spec/controllers/pages_controller_spec.rb:42:in `block (4 levels) in <top (required)>'
If I change site.pages in first test to Page - it's actually working.
So I am really confused how to fix this tests and where is the mistake.
Solved
Problem was with my PagesController, method create should look like this
def create
#page = #site.pages.build(page_params)
#page.user = current_user
#page.save
respond_with(#page)
end
Problem was with my PagesController, method create should look like this
def create
#page = #site.pages.build(page_params)
#page.user = current_user
#page.save
respond_with(#page)
end
I have route definition
post '/session/create' => 'sessions#create', as: 'create_session'
controller
class SessionsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token, :only => :create
def create
# do some staff
end
end
If I send post request using RestConsole - it works like it should, but if I try to send post from RSpec test like below - it throws exception.
require 'spec_helper'
describe SessionsController do
let(:user_data_to_post) { FactoryGirl.attributes_for :user }
let(:user) do
mock_model User
end
describe 'create new session should be successful' do
before do
post(create_session_path, user_data_to_post.to_json,
{'HTTP_CONTENT_TYPE' => 'application/json'})
end
it "should call search_by_email of User model" do
User.should_recive(:search_by_email).with(user_data_to_post[:email])
end
end
end
error message:
Failure/Error: post(create_session_path, user_data_to_post.to_json,
ActionController::UrlGenerationError:
No route matches {:HTTP_CONTENT_TYPE=>"application/json", :controller=>"sessions", :action=>"/session/create"}
# ./spec/controllers/sessions_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Try:
post :create, user_data_to_post, format: :json
Firstly, post method expects the name of the action on the controller you passed to initial describe. Since you passed the path instead, it was treating the whole path as an action name, which obviously was not routed.
Secondly, post method signature is sth in line post(action_name, raw_post_data(assigned only if string), params=nil, session=nil, flash=nil) (in fact it is post(action_name, *args) but this is how those args are being processed. Sine you wanted to modify headers, you need to do this directly on a request:
#request['HTTP_CONTENT_TYPE'] = 'application/json'
or to pass format: :json along with params
I'm having trouble setting expectations for a mock used in one of my controllers:
controllers/blark_controller.rb
class BlarkController < ApplicationController
def show
user = User.first
user.inspect
render nothing: true
end
end
spec/controllers/blark_controller_spec.rb
require 'spec_helper'
describe BlarkController do
describe 'GET :show' do
let(:user) { mock_model User }
before do
User.stub(:first).and_return(user)
get :show
end
it 'blarks' do
expect(user).to receive(:inspect)
end
end
end
Results in this:
22:04:58 - INFO - Running: spec/controllers/blark_controller_spec.rb
BlarkController
GET :show
blarks (FAILED - 1)
Failures:
1) BlarkController GET :show blarks
Failure/Error: expect(user).to receive(:inspect)
(Double "User_1001").inspect(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
# ./spec/controllers/blark_controller_spec.rb:14:in `block (3 levels) in <top (required)>'
Finished in 0.15579 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/blark_controller_spec.rb:13 # BlarkController GET :show blarks
Can I set expectations on mocks in specs?
You can, but the way you're doing it is wrong.
You're calling an action (get :show) and then after calling it, setting a future expectation (expect(user).to receive(:inspect)). Obvious this won't work because you've already called the action, there is no future for this test.
You either need to set the expectation before calling the action (switch the order of the statements) or use rspec's recently-added spies feature to set expectations after the fact. This uses have_received rather than receive.
For more detail: https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/spies/spy-on-a-stubbed-method-on-a-partial-mock
When building my app, I generated scaffolds, which created standard Rspec tests. I'd like to use these tests for coverage, but they seem to be failing due to nested routes:
When I run the test, this is its feedback:
Failures:
1) ListItemsController routing routes to #index
Failure/Error: get("/list_items").should route_to("list_items#index")
No route matches "/list_items"
# ./spec/routing/list_items_routing_spec.rb:7:in `block (3 levels) in <top (required)>'
Finished in 0.25616 seconds
1 example, 1 failure
How do I tell Rspec that there are nested routes?
Here are the abridged files:
list_items_routing_spec.rb:
require "spec_helper"
describe ListItemsController do
describe "routing" do
it "routes to #index" do
get("/list_items").should route_to("list_items#index")
end
end
list_items_controller_spec.rb:
describe ListItemsController do
# This should return the minimal set of attributes required to create a valid
# ListItem. As you add validations to ListItem, be sure to
# adjust the attributes here as well.
let(:valid_attributes) { { "list_id" => "1", "project_id" => "1" } }
# 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
# ListItemsController. Be sure to keep this updated too.
let(:valid_session) { {} }
describe "GET index" do
it "assigns all list_items as #list_items" do
list_item = ListItem.create! valid_attributes
get :index, project_id: 2, {}, valid_session
assigns(:list_items).should eq([list_item])
end
end
routes.rb:
resources :projects do
member do
match "list_items"
end
end
Notes:
- I've tried changing the rpec tests themselves to include a project_id, and that didn't help.
- I'm using Factory Girl for fixture generation (not sure if this is relevant)
Thanks for your help!
First of all, run rake routes to see what routes exist.
According to what you have in your routes I would expect you have a ProjectsController that has an action list_items. This action would be available under /projects/:id/list_items.
Now I can only theorize about what really you want, but I will guess.
If you want /projects/:project_id/list_items to route to list_items#index you have to change your routes to:
resources :projects do
resources :list_items
end
You can confirm that by running rake routes.
Then fix the assertion in your routing spec:
get("/projects/23/list_items").should route_to("list_items#index", :project_id => "23")
Update for RSpec v2.14+ Expectations
expect(:get => "/projects/23/list_items").to route_to("list_items#index", :project_id => "23")
What am I forgetting?
routes:
get "/comingsoon" => "visitors#comingsoon"
resources :visitors
controller:
class VisitorsController < ApplicationController
def comingsoon
#new_visitor = Visitor.new
end
end
spec:
require 'spec_helper'
describe VisitorsController do
describe "GET /comingsoon" do
it "should be happy" do
get "/comingsoon"
response.should be_success
end
end
end
And here's the result:
✗ rspec spec/controllers/visitors_controller_spec.rb
F
Failures:
1) VisitorsController GET /comingsoon should be valid
Failure/Error: get "/comingsoon"
ActionController::RoutingError:
No route matches {:controller=>"visitors", :action=>"/comingsoon"}
# ./spec/controllers/visitors_controller_spec.rb:7:in `block (3 levels) in <top (required)>'
Finished in 0.14226 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/controllers/visitors_controller_spec.rb:6 # VisitorsController GET /comingsoon should be valid
What am I forgetting?
In your spec file replace get "/comingsoon"
with get "comingsoon"
When you spec a controller with rspec the operand of the http verb (get, post, put, delete) is an action of the controller rather than a url.
Possibly daft suggestion, but you have a view right? Otherwise you have to tell your controller to render something.