Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authenticated Route not working for Rspec test

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?

like image 601
chell Avatar asked Sep 06 '16 08:09

chell


1 Answers

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.

like image 65
Ninigi Avatar answered Sep 26 '22 23:09

Ninigi