I'm following this post about setting up authentication in the routes of my Rails 4 application.
Here is my routes.rb file:
Rails.application.routes.draw do devise_for :employees, :controllers => { registrations: 'employees/registrations' } devise_for :clients authenticate :employee do resources :quotation_requests, only: [:show, :edit,:index, :update, :destroy] end resources :quotation_requests, only: [:new, :create] get '/dashboard' => 'dashboard#show', as: 'show_dashboard' root to: 'home#index' end
Here is my quotation_requests_controller_spec.rb file:
require 'rails_helper' RSpec.describe QuotationRequestsController, type: :controller do describe "GET index" do it "renders :index template" do get :index expect(response).to render_template(:index) end it "assigns quotation requests to template" do quotation_requests = FactoryGirl.create_list(:quotation_request, 3) get :index expect(assigns(:quotation_requests)).to match_array(quotation_requests) end end describe "GET edit" do let(:quotation_request) { FactoryGirl.create(:quotation_request)} it "renders :edit template" do get :edit, id: quotation_request expect(response).to render_template(:edit) end it "assigns the requested quotation request to template" do get :edit, id: quotation_request expect(assigns(:quotation_request)).to eq(quotation_request) end end describe "PUT update" do let(:quotation_request) { FactoryGirl.create(:quotation_request)} context "valid data" do new_text = Faker::Lorem.sentence(word_count=500) let(:valid_data) { FactoryGirl.attributes_for(:quotation_request, sample_text: new_text)} it "redirects to quotation_request#showtemplate" do put :update, id: quotation_request, quotation_request: valid_data expect(response).to redirect_to(quotation_request) end it "updates quotation request in the database" do put :update, id: quotation_request, quotation_request: valid_data quotation_request.reload #need to reload the object because we have just updated it in the database so need to get the new values expect(quotation_request.sample_text).to eq(new_text) end end context "invalid data" do let(:invalid_data) { FactoryGirl.attributes_for(:quotation_request, sample_text: "", number_of_words: 400)} it "renders the :edit template" do put :update, id: quotation_request, quotation_request: invalid_data expect(response).to render_template(:edit) end it "does not update the quotation_request in the database" do put :update, id: quotation_request, quotation_request: invalid_data quotation_request.reload expect(quotation_request.number_of_words).not_to eq(400) end end end describe "GET new", new: true do it "renders :new template" do get :new expect(response).to render_template(:new) end it "assigns new QuotationRequest to @quotation_request" do get :new expect(assigns(:quotation_request)).to be_a_new(QuotationRequest) end end describe "GET show" do #this test requires that there be a quotation request in the database let(:quotation_request) { FactoryGirl.create(:quotation_request) } context 'invalid request' do it "does not render :show template if an employee or client is not signed in" do #setup quotation_request = create(:quotation_request) #exercise get :show, id: quotation_request #verification expect(response).to_not render_template(:show) end end context 'valid request' do sign_in_proofreader it "renders :show template if an employee or client is signed in" do #setup quotation_request = create(:quotation_request) #exercise get :show, id: quotation_request #verification expect(response).to render_template(:show) end it "assigns requested quotation_request to @quotation_request" do get :show, id: quotation_request expect(assigns(:quotation_request)).to eq(quotation_request) end end end describe "POST create", post: true do context "valid data" do let(:valid_data) {FactoryGirl.nested_attributes_for(:quotation_request)} it "redirects to quotation_requests#show" do post :create, quotation_request: valid_data expect(response).to redirect_to(quotation_request_path(assigns[:quotation_request])) end it "creates new quotation_request in database" do expect { post :create, quotation_request: valid_data }.to change(QuotationRequest, :count).by(1) end end context "invalid data" do let(:invalid_data) {FactoryGirl.nested_attributes_for(:quotation_request).merge(sample_text: 'not enough sample text')} it "renders :new template" do post :create, quotation_request: invalid_data expect(response).to render_template(:new) end it "doesn't creates new quotation_request in database" do expect { post :create, quotation_request: invalid_data }.not_to change(QuotationRequest, :count) end end end describe "DELETE destroy" do let(:quotation_request) { FactoryGirl.create(:quotation_request) } it "redirects to the quotation request#index" do delete :destroy, id: quotation_request expect(response).to redirect_to(quotation_requests_path) end it "delets the quotation request from the database" do delete :destroy, id: quotation_request expect(QuotationRequest.exists?(quotation_request.id)).to be_falsy end end end
My quotation_requests_controller.rb
class QuotationRequestsController < ApplicationController # before_action :authenticate_employee!, :only => [:show] def index @quotation_requests = QuotationRequest.all end def new @quotation_request = QuotationRequest.new @quotation_request.build_client end def edit @quotation_request = QuotationRequest.find(params[:id]) end def create client = Client.find_or_create(quotation_request_params[:client_attributes]) @quotation_request = QuotationRequest.new(quotation_request_params.except(:client_attributes).merge(client: client)) if @quotation_request.save ClientMailer.quotation_request_created(client.email, @quotation_request.id).deliver_now redirect_to @quotation_request, notice: 'Thank you.' else render :new end end def show @quotation_request = QuotationRequest.find(params[:id]) end def update @quotation_request = QuotationRequest.find(params[:id]) if @quotation_request.update(quotation_request_params) redirect_to @quotation_request else render :edit end end def destroy QuotationRequest.destroy(params[:id]) redirect_to quotation_requests_path end private def quotation_request_params params.require(:quotation_request).permit(:number_of_words, :return_date, :sample_text, :client_attributes => [:first_name, :last_name, :email]) end end
I know the routes authentication works because if I test them in the browser I get redirected to the sign_in page. However, the tests don't pass in Rspec.
if I put this code in the quotation_requests_controller.rb:
before_action :authenticate_employee!, :only => [:show]
The rspec tests pass. So for some reason Rspec does not register the authentication of the routes.
Here is the output from Rspec for the tests run with the authenticated routes:
QuotationRequestsController GET index valid request renders :index template for signed in employee assigns quotation requests to template invalid request does not render :index template without a signed in employee (FAILED - 1) GET edit valid request renders :edit template with a signed in employee assigns the requested quotation request to template invalid request does not render the :edit template without a signed in employee (FAILED - 2) PUT update valid request valid data redirects to quotation_request#showtemplate updates quotation request in the database invalid data renders the :edit template does not update the quotation_request in the database invalid request redirects user to the sign in page (FAILED - 3) GET new renders :new template assigns new QuotationRequest to @quotation_request GET show invalid request does not render :show template if an employee or client is not signed in (FAILED - 4) valid request renders :show template if an employee or client is signed in assigns requested quotation_request to @quotation_request POST create valid data redirects to quotation_requests#show creates new quotation_request in database invalid data renders :new template doesn't creates new quotation_request in database DELETE destroy valid request redirects to the quotation request#index delets the quotation request from the database invalid request does not delete the quotation request without a signed in employee (FAILED - 5) Failures: 1) QuotationRequestsController GET index invalid request does not render :index template without a signed in employee Failure/Error: expect(response).to_not render_template(:index) Didn't expect to render index # ./spec/controllers/quotation_requests_controller_spec.rb:43:in `block (4 levels) in <top (required)>' # -e:1:in `<main>' 2) QuotationRequestsController GET edit invalid request does not render the :edit template without a signed in employee Failure/Error: expect(response).to_not render_template(:edit) Didn't expect to render edit # ./spec/controllers/quotation_requests_controller_spec.rb:92:in `block (4 levels) in <top (required)>' # -e:1:in `<main>' 3) QuotationRequestsController PUT update invalid request redirects user to the sign in page Failure/Error: expect(response).to_not redirect_to(quotation_request) Didn't expect to redirect to #<QuotationRequest:0x007fe7eb69c8c0> # ./spec/controllers/quotation_requests_controller_spec.rb:182:in `block (4 levels) in <top (required)>' # -e:1:in `<main>' 4) QuotationRequestsController GET show invalid request does not render :show template if an employee or client is not signed in Failure/Error: expect(response).to_not render_template(:show) Didn't expect to render show # ./spec/controllers/quotation_requests_controller_spec.rb:217:in `block (4 levels) in <top (required)>' # -e:1:in `<main>' 5) QuotationRequestsController DELETE destroy invalid request does not delete the quotation request without a signed in employee Failure/Error: expect(QuotationRequest.exists?(quotation_request.id)).to be_truthy expected: truthy value got: false # ./spec/controllers/quotation_requests_controller_spec.rb:361:in `block (4 levels) in <top (required)>' # -e:1:in `<main>' Finished in 2.11 seconds (files took 1.75 seconds to load) 23 examples, 5 failures Failed examples: rspec ./spec/controllers/quotation_requests_controller_spec.rb:37 # QuotationRequestsController GET index invalid request does not render :index template without a signed in employee rspec ./spec/controllers/quotation_requests_controller_spec.rb:83 # QuotationRequestsController GET edit invalid request does not render the :edit template without a signed in employee rspec ./spec/controllers/quotation_requests_controller_spec.rb:171 # QuotationRequestsController PUT update invalid request redirects user to the sign in page rspec ./spec/controllers/quotation_requests_controller_spec.rb:208 # QuotationRequestsController GET show invalid request does not render :show template if an employee or client is not signed in rspec ./spec/cont
Why do the routes I have written not work in Rspec tests?
1 Answers
Answers 1
I take it you are using rspec-rails in your rails app. Rspec-rails sets up a lot of convenience methods for you, but it also introduces some black-magic, which can lead to some unexpected results - like this.
As you can see here it is explained in the comments for controller specs:
# Supports a simple DSL for specifying behavior of ApplicationController. # Creates an anonymous subclass of ApplicationController and evals the # `body` in that context. Also sets up implicit routes for this # controller, that are separate from those defined in "config/routes.rb".
I guess the logic here is, controller features are different from routing and should be tested separately (and indeed rspec-rails offers a test group for routing), so we do not need the routes for controller specs, meaning you should be able to test your controller without setting up the routes.
In my oppinion, testing the redirect for unauthenticated users is more of an integration test, since it requires multiple parts of your application to work together and as such should not be tested in the controller context, but rather as a feature in some blackbox test.
You can write integration tests by placing them in one of these directories spec/requests, spec/api, and spec/integration
or by explicitely declaring their type with
RSpec.describe "Something", type: :request do
or place it in spec/features
or declare the type as
RSpec.describe "Something", type: :feature do
depending on which level you want to test the redirect (meaning: only test the request-response cycle, or run it in a simulated browser). Please refer to the documentation for integration tests on the rspec-rails github page for more information.
0 comments:
Post a Comment