Home > Back-end >  Rails - How to pass controller tests when I use before_action
Rails - How to pass controller tests when I use before_action

Time:04-03

I need to run some tests and I have come at a stand still here.

I am using before_action in my Appointments controller

Here is the controller

class AppointmentsController < ApplicationController
  before_action :set_appointment, only: %i[ show edit update destroy ]
  #before we run anything if the user is not signed in show index and show functions
  before_action :authenticate_user!, except: [:index,:show]
  #only the correct user can edit,update and destroy
  before_action :correct_user, only: [:edit, :update , :destroy]

  # GET /appointments or /appointments.json
  def index
    @appointments = Appointment.all.decorate
  end

  # GET /appointments/1 or /appointments/1.json
  def show
  end

  # GET /appointments/new
  def new
    #@appointment = Appointment.new
    @appointment = current_user.appointments.build
  end

  # GET /appointments/1/edit
  def edit
  end

  #function to allow for search functionality 
  def search
    @appointments = Appointment.where("date LIKE?", "%" params[:q] "%")
  end

  # POST /appointments or /appointments.json
  def create
   #@appointment = Appointment.new(appointment_params)
   @appointment = current_user.appointments.build(appointment_params)
   #here underneath I am using my custom gem to filter bad words within the notes field when creating an appointment
   @appointment.notes = Badwordgem::Base.sanitize(@appointment.notes)
    respond_to do |format|
      if @appointment.save
        format.html { redirect_to appointment_url(@appointment), notice: "Appointment was successfully created." }
        format.json { render :show, status: :created, location: @appointment }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @appointment.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /appointments/1 or /appointments/1.json
  def update
    respond_to do |format|
      if @appointment.update(appointment_params)
        format.html { redirect_to appointment_url(@appointment), notice: "Appointment was successfully updated." }
        format.json { render :show, status: :ok, location: @appointment }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @appointment.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /appointments/1 or /appointments/1.json
  def destroy
    @appointment.destroy

    respond_to do |format|
      format.html { redirect_to appointments_url, notice: "Appointment was successfully destroyed." }
      format.json { head :no_content }
    end
  end

#function here that restricts editing so the current logged in user can edit only their records
def correct_user
  @appointment = current_user.appointments.find_by(id: params[:id])
  redirect_to appointments_path, notice:"NOT ALLOWED TO EDIT THIS" if @appointment.nil?
end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_appointment
      @appointment = Appointment.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def appointment_params
      params.require(:appointment).permit(:barber, :customer, :notes, :date,:user_id)
    end
end

and here is my test controller

require "test_helper"

class AppointmentsControllerTest < ActionDispatch::IntegrationTest
  Devise::Test::IntegrationHelpers
  setup do
    @appointment = appointments(:one)
  end

  test "should get index" do
    get appointments_url
    assert_response :success
  end

  test "should get new" do
    get new_appointment_url
    assert_response :success
  end

  test "should create appointment" do
    assert_difference('Appointment.count') do
      post appointments_url, params: { appointment: { barber: @appointment.barber, customer: @appointment.customer, date: @appointment.date, notes: @appointment.notes } }
    end

    assert_redirected_to appointment_url(Appointment.last)
  end

  test "should show appointment" do
    get appointment_url(@appointment)
    assert_response :success
  end

  test "should get edit" do
    get edit_appointment_url(@appointment)
    assert_response :success
  end

  test "should update appointment" do
    patch appointment_url(@appointment), params: { appointment: { barber: @appointment.barber, customer: @appointment.customer, date: @appointment.date, notes: @appointment.notes } }
    assert_redirected_to appointment_url(@appointment)
  end

  test "should destroy appointment" do
    assert_difference('Appointment.count', -1) do
      delete appointment_url(@appointment)
    end

    assert_redirected_to appointments_url
  end
end

If I comment out the "before actions" in my controller , of course all the tests pass but with them 15 tests fail. How do I make the tests pass with the before_action ?

CodePudding user response:

Just need to set up devise helpers properly. https://github.com/heartcombo/devise#integration-tests

In general you have to set up your requests and your models according to what the controller expects.

For before_action :authenticate_user! user must be authenticated; have a current session during the test, i.e. a particular cookie must be set or whatever the authentication mechanism expects. This is taken care of by device sign_in helper. When signed in the current_user helper will return a user model instance.

For before_action :correct_user user must be authorized to access a resource. This depends on the logic of correct_user method—appointment has to belong to the current user. Set this up in a test or in fixtures.

# test/controllers/appointments_controller_test.rb

require "test_helper"

class AppointmentsControllerTest < ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers
  
  setup do
    @appointment = appointments(:one)
    @user = users(:user)
    
    # assign appointment to user to pass :edit, :update, :destroy actions
    # TODO: do this in fixtures/factories
    @user.appointments << @appointment
    
    sign_in @user

    # NOTE: how to sign out
    # sign_out @user
    #  or
    # sign_out :user
  end
  
  # ...
end

Add a user fixture. Don't use password attribute, it doesn't exist in the database.

# test/fixtures/users.yml

user:
  email: [email protected]
  encrypted_password: doesnt-matter
  # NOTE: if you need to know the password
  # encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>

CodePudding user response:

I think that the first before_action don't cause problems. I am right? So for the second you need to authenticate a user and send the token or cookie with the request for pass the before_action ":authenticate_user!" and check if the user you authenticate has the right access for pass ":correct_user".

I can see you are using Devise for your authentication methods, so you can use the sign_in method for a user created on your fixtures. The docs here

  • Related