Sunday, September 25, 2016

Authenticated Route not working for Rspec test

Leave a Comment

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment