I've got routes setup so that they work as expected in my controllers; I can use both room_path and rooms_path as expected.
However when I try to use the same routes in a controller spec for some reason then I get an error:
ActionController::UrlGenerationError:
No route matches {:action=>"/1", :controller=>"rooms"}
My routes.rb file:
root "rooms#index"
resources :rooms, :path => '/', only: [:index, :create, :show] do
resources :connections, only: [:create,:destroy]
end
And if I rake routes:
room_connections POST /:room_id/connections(.:format) connections#create
room_connection DELETE /:room_id/connections/:id(.:format) connections#destroy
rooms GET / rooms#index
POST / rooms#create
room GET /:id(.:format) rooms#show
However my test fails:
describe "GET room_path(room)" do
it "renders show" do
#room = Room.create
get room_path(#room)
expect(response.status).to eq(200)
expect(response).to render_template(:show)
end
end
While my controllers can use the same route helpers without issue:
class RoomsController < ApplicationController
def index
end
def create
#room = Room.create
redirect_to room_path(#room)
end
def show
#room = Room.find(params[:id])
end
end
I'm not sure why in my tests it seems to go looking for a "/1" action rather than rooms#show like I would expect.
Update
So continuing to play this I've been able to get the test green by changing to the following:
describe "GET room_path(room)" do
it "renders show" do
#room = Room.create
get :show, params: { id: #room.id }
expect(response.status).to eq(200)
expect(response).to render_template(:show)
end
end
I would still love to understand why my helpers aren't working though. Is this to be expected? Manually writing the Parameters hash is kind of a PITA.
I don't know what version of Rails and RSpec you're on. This works for me on Rails 4.2 and Rspec 3.4.4:
describe "my neat test description", type: :routing do
it "can use path helper" do
puts whatever_path
end
end
The type: :routing pulls in the path and url helpers.
I believe the reason you don't have that by default is that rspec is replicating a lot of the environment for the different types of tests. For controller tests, it's pulling in the various path and url helpers because generally speaking they're used there often enough to be worth pulling in. In model tests, for example, they aren't used there often so they aren't pulled in by default.
These helpers live on the app object.
Related
In my Rails 5 app I have this:
class InvoicesController < ApplicationController
def index
#invoices = current_account.invoices
respond_to do |format|
format.csv do
invoices_file(:csv)
end
format.xml do
invoices_file(:xml)
end
end
end
private
def invoices_file(type)
headers['Content-Disposition'] = "inline; filename=\"invoices.#{type.to_s}\""
end
end
describe InvoicesController, :type => :controller do
it "renders a csv attachment" do
get :index, :params => {:format => :csv}
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
My problem is that my Spec always passes (!), even when I put a bunch of crap into my index.csv.erb file. It seems that the view file isn't even evaluated / tested by RSpec.
How is this possible? What am I missing here?
Controller tests/specs are these weird stubbed creations born out of the idea of unit testing controllers in isolation. That idea turned out to be pretty flawed and has really fallen out of vogue lately.
Controller specs don't actually make a real HTTP request to your application that passes through the routes. Rather they just kind of fake it and pass a fake request through.
To make the tests faster they also don't really render the views either. Thats why it does not error out as you have expected. And the response is not really a real rack response object either.
You can make RSpec render the views with render_views.
describe InvoicesController, :type => :controller do
render_views
it "renders a csv attachment" do
get :index, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(response).to render_template :index
end
end
But a better and more future proof option is using a request spec.
The official recommendation of the Rails team and the RSpec core team
is to write request specs instead. Request specs allow you to focus on
a single controller action, but unlike controller tests involve the
router, the middleware stack, and both rack requests and responses.
This adds realism to the test that you are writing, and helps avoid
many of the issues that are common in controller specs.
http://rspec.info/blog/2016/07/rspec-3-5-has-been-released/
# spec/requests/invoices
require 'rails_helper'
require 'csv'
RSpec.describe "Invoices", type: :request do
let(:csv) { response.body.parse_csv }
# Group by the route
describe "GET /invoices" do
it "renders a csv attachment" do
get invoices_path, format: :csv
expect(response.headers["Content-Type"]).to eq("text/csv; charset=utf-8")
expect(response).to have_http_status(200)
expect(csv).to eq ["foo", "bar"] # just an example
end
end
end
The format option should be specified outside of the params, i.e. get :index, params: {}, format: :csv}.
Regarding RSpec evaluating views, no, in controller tests, it doesn't, regardless of the format. However, it's possible to test views with RSpec: https://relishapp.com/rspec/rspec-rails/v/2-0/docs/view-specs/view-spec
I'm learning RSpec by writing specs for an existing project. I'm having trouble with a controller spec for a polymorphic resource Notes. Virtually any other model can have a relationship with Notes like this: has_many :notes, as: :noteable
In addition, the app is multi-tenant, where each Account can have many Users. Each Account is accessed by :slug instead of :id in the URL. So my mulit-tenant, polymorphic routing looks like this:
# config/routes.rb
...
scope ':slug', module: 'accounts' do
...
resources :customers do
resources :notes
end
resources :products do
resources :notes
end
end
This results in routes like this for the :new action
new_customer_note GET /:slug/customers/:customer_id/notes/new(.:format) accounts/notes#new
new_product_note GET /:slug/products/:product_id/notes/new(.:format) accounts/notes#new
Now on to the testing problem. First, here's an example of how I test other non-polymorphic controllers, like invitations_controller:
# from spec/controllers/accounts/invitation_controller_spec.rb
require 'rails_helper'
describe Accounts::InvitationsController do
describe 'creating and sending invitation' do
before :each do
#owner = create(:user)
sign_in #owner
#account = create(:account, owner: #owner)
end
describe 'GET #new' do
it "assigns a new Invitation to #invitation" do
get :new, slug: #account.slug
expect(assigns(:invitation)).to be_a_new(Invitation)
end
end
...
end
When i try to use a similar approach to test the polymorphic notes_controller, I get confused :-)
# from spec/controllers/accounts/notes_controller_spec.rb
require 'rails_helper'
describe Accounts::NotesController do
before :each do
#owner = create(:user)
sign_in #owner
#account = create(:account, owner: #owner)
#noteable = create(:customer, account: #account)
end
describe 'GET #new' do
it 'assigns a new note to #note for the noteable object' do
get :new, slug: #account.slug, noteable: #noteable # no idea how to fix this :-)
expect(:note).to be_a_new(:note)
end
end
end
Here I'm just creating a Customer as #noteable in the before block, but it could just as well have been a Product. When I run rspec, I get this error:
No route matches {:action=>"new", :controller=>"accounts/notes", :noteable=>"1", :slug=>"nicolaswisozk"}
I see what the problem is, but i just can't figure out how to address the dynamic parts of the URL, like /products/ or /customers/.
Any help is appreciated :-)
UPDATE:
Changed the get :new line as requested below to
get :new, slug: #account.slug, customer_id: #noteable
and this causes the error
Failure/Error: expect(:note).to be_a_new(:note)
TypeError:
class or module required
# ./spec/controllers/accounts/notes_controller_spec.rb:16:in `block (3 levels) in <top (required)>'
Line 16 in the spec is:
expect(:note).to be_a_new(:note)
Could this be because the :new action in my notes_controller.rb is not just a #note = Note.new, but is initializing a new Note on a #noteable, like this?:
def new
#noteable = find_noteable
#note = #noteable.notes.new
end
Well the problem here should be that in this line
get :new, slug: #account.slug, noteable: #noteable
you are passing :noteable in params. But, you need to pass all the dynamic parts of the url instead to help rails match the routes. Here you need to pass :customer_id in params. Like this,
get :new, slug: #account.slug, customer_id: #noteable.id
Please let me know if this helps.
Given I set up a HomeController with an index action
class HomeController < ApplicationController
def index
#users = User.all
end
end
and routed to it via the root path,
root :to => "home#index"
why does this request spec fail
it 'should called the home#index action' do
HomeController.should_receive(:index)
visit root_path
end
with the following message
Failure/Error: HomeController.should_receive(:index)
(<HomeController (class)>).index(any args)
expected: 1 time
received: 0 times
? Is it because the index method is called as a instance method instead of a class method?
I'm not sure exactly what you want to test, and I think there's some confusion as to what methods can be used where, so I'll try and give examples of Routing specs, Request Specs, Controller specs, and Feature specs, and hopefully one of them will be appropriate for you.
Routing
If you want to make sure that your root path gets routed to the home#index action, a routing spec may be appropriate:
spec/routing/routing_spec.rb
describe "Routing" do
it "routes / to home#index" do
expect(get("/")).to route_to("home#index")
end
end
Request
If you want to make sure that the index template gets rendered on a request to your root path, a request spec may be appropriate:
spec/requests/home_requests_spec.rb
describe "Home requests" do
it 'successfully renders the index template on GET /' do
get "/"
expect(response).to be_successful
expect(response).to render_template(:index)
end
end
Controller
If you want to make sure that the index template gets rendered on a request to the index action of your HomeController, a controller spec may be appropriate (and quite similar to a request spec in this case, but focused exclusively on the controller):
spec/controllers/home_controller_spec.rb
describe HomeController do
describe "GET index" do
it "successfully renders the index template" do
expect(controller).to receive(:index) # this line probably of dubious value
get :index
expect(response).to be_successful
expect(response).to render_template(:index)
end
end
end
Feature
If you want to make sure the page rendered by home#index has some specific content, a feature spec may be appropriate (and also the only place you can use Capybara methods like visit, depending on your Rails/RSpec version):
spec/features/home_features_spec.rb
feature "Index page" do
scenario "viewing the index page" do
visit root_path
expect(page).to have_text("Welcome to my awesome index page!")
end
end
class MyController < ApplicationController
def index
my_method
end
def my_method
end
end
describe MyController do
it 'calls my method' do
expect(controller).to receive(:my_method)
get :index
end
end
I'm creating an API in Rails and I use versionist to handle versions. I want to test API controllers, but I'm unable to create a valid request.
My controller:
class Api::V1::ItemsController < Api::V1::BaseController
def index
render json:'anything'
end
end
My spec:
describe Api::V1::ItemsController do
describe "#create" do
it "shows items" do
get :index, format: :json
end
end
end
routes.rb:
scope '/api' do
api_version(:module => "Api::V1", :path => {:value => "v1"}, :default => true) do
resources :items
end
end
The test doesn't check anything. Still, it raises an error:
Failure/Error: get :index, format: :json
ActionController::RoutingError:
No route matches {:format=>:json, :controller=>"api/v1/items", :action=>"index"}
I suppose that there is something wrong with the :controller key in the request, but I don't know how to fix it...
I was able to reproduce this locally. You need to move this to a request spec instead of a controller spec for this to work:
# spec/requests/api/v1/items_controller_spec.rb
describe Api::V1::ItemsController do
describe "#index" do
it "shows items" do
get '/api/v1/items.json'
# assert something
end
end
end
The versionist documentation says you need to do this when using the HTTP header or request parameter versioning strategies (https://github.com/bploetz/versionist#a-note-about-testing-when-using-the-http-header-or-request-parameter-strategies) but that's clearly not the case here. I'll file an issue to get this clarified in the documentation that you need to do this for all versioning strategies.
i am working in Rspec of ROR..
I am trying to test my controllers using RSpec.i am having a Users controller with functions like new , tags, etc..
i created a file under spec/users_controller_spec.rb
and added the test cases as.
require 'spec_helper'
describe UsersController do
integrate_views
it "should use UsersController" do
controller.should be_an_instance_of(UsersController)
end
describe "GET 'new'" do
it "should be successful" do
get 'new'
response.should be_success
end
it "should have the title" do
get 'new'
response.should have_tag("title", "First app" )
end
end
end
which gets pass.
But when i add a test case for tags ..
like
describe "GET 'tags'" do
it "should be successful" do
get 'tags'
response.should be_success
end
end
this results in an error as
F...
1)
'UsersController GET 'tags' should be successful' FAILED
expected success? to return true, got false
why it is coming like this ?? i am very new to ROR and cant find the reason of why i am getting this error..
How to make this pass .
Also i tried the Url
http://localhost:3000/users/tags which is running for me .. But on testing using $spec spec/ i am getting the error ..
Your test may be failing for any number of reasons. Does the route require an ID in the parameter hash? Is the controller action redirecting? Is the controller raising an error?
You'll need to look at the controller code /and/or routes.rb to discover the cause of the failure. Take note of before filters in the controller, which may not allow the action to be reachable at all.
You need to add custom routes that are not within the default 7 routes. Assuming you have resources :users within your routes you will need to modify it. I'm also assuming that your tags route is unique to individual users.
resources :users do
member do
# creates /users/:user_id/tags
get :tags
end
end
And in your RSpec test you would call it like
describe '#tags' do
user = create :user
get :tags, user_id: user.id
end
If the route is not to be unique per user the other option is a collection route, something like:
resources :users do
collection do
# creates /users/tags
get :tags
end
end