Yingqi
Yingqi

Reputation: 1515

test update request with RSpec failed

I have two problems when I try to test the update action with RSpec, here is the controller file:

#volunteers_controller.rb

module Api
  module V1
    class VolunteersController < ApplicationController
      before_action :find_volunteer, only: %i[show update destroy]

      def update
        @volunteer.update!(volunteer_params)
        head :no_content
      end

      private

      def find_volunteer
        @volunteer = Volunteer.find_by!(id: params[:id])
      end

      def volunteer_params
        params.require(:volunteer).permit(:image_url, :name, :job_desc)
      end

    end
  end
end

Here is the test file:

require 'rails_helper'

RSpec.describe Api::V1::VolunteersController, type: :request do
   ...

    describe '#update' do
       let(:volunteer) { Volunteer.create!( :image_url=>"first.jpg", :name=>"test1", :job_desc=>"description") }       
       let(:params){
            {:volunteer =>  {
                "image_url"=>"new.jpg", 
                "name"=>"test1", 
                "job_desc"=>"description"
            }
            }
        }

        it 'updates a certain volunteer' do
            patch :patch, :params => params #failed, bad URL
            expect(volunteer.image_url).to eq("new.jpg") #failed, still return 'first.jpg'
        end



        it 'returns a no_content header' do
            patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params
            expect(response).to have_http_status "204"
        end

    end

end

private

   def json_parse(string)
     if string.class==String
       json = JSON.parse(string)
     end
       json
    end

So my questions are:

  1. when try to write the URL like this: patch :patch, :params => params, I got the following error:
 Api::V1::VolunteersController#update updates a certain volunteer
     Failure/Error: patch :patch, :params => params

     URI::InvalidURIError:
       bad URI(is not URI?): "http://www.example.com:80patch"

How can I change the URL to: "http://localhost:3000/api/v1/volunteers/#{volunteer.id}"?

  1. I manually test the update action, putting a binding.pry in the update action, it does update volunteer subject, however, when it goes back to the test, it shows that it doesn't not get updated, why is that?

Thank you!!

Upvotes: 0

Views: 1832

Answers (1)

max
max

Reputation: 101891

The first problem is really your update method itself and its complete lack of error handling and meaningful feedback to the client. update! will raise ActiveRecord::RecordInvalid if the input is invalid - which is not rescued at all in your controller. And exceptions should no be used for normal code flow - invalid input is not really an exceptional event.

Instead you should rewrite your controller so that it checks if the update is performed and returns the appropriate response:

def update
  if @volunteer.update(volunteer_params)
    head :no_content
  else
    head :unprocessable_entity
  end
end

As for the spec itself you're mixing up controller specs and request specs. While they look somewhat similar the key difference is that a request spec sends actual HTTP requests your rails server while a controller spec stubs the actual request and passes it to an instance of the controller under test.

In a controller spec you could write:

patch :update, params: { ... }

Because its actually calling the update method on an instance of the controller. But of course:

patch :patch, :params => params #failed, bad URL

Will not work in request spec since its not a valid URL and request specs send actual HTTP requests. Note that you should pass relative URLs and not absolute URLs as the test server may run on a different port then the dev server

# Bad
patch "http://localhost:3000/api/v1/volunteers/#{volunteer.id}", :params => params

# Good
patch "/api/v1/volunteers/#{volunteer.id}", params: params 

ActiveRecord models are not "live reloading" - the representation in memory will not automatically be updated when the values in the database are updated. You need to manaully reload the record for that to happen:

it 'updates a certain volunteer' do
  patch "/api/v1/volunteers/#{volunteer.id}", params: params
  volunteer.reload
  expect(volunteer.image_url).to eq("new.jpg")
end

Altogether your spec should actually look something like:

# Describe the endpoint - not the controller implmentation
RSpec.describe "V1 Volunteers API", type: :request do
  describe 'PATCH /api/v1/volunteers/:id' do
    # use do ... end if the expression does not fit on one line
    let(:volunteer) do 
      # enough with the hashrockets already! 
      Volunteer.create!(
        image_url: "first.jpg", 
        name: "test1", 
        job_desc: "description"
      ) 
    end

    context "with invalid parameters" do
      # some set of failing parameters
      let(:params) do
        {
          volunteer:  {
            name: ""
          }
        }
      end

      it "returns unproccessable entity" do
        patch "/api/v1/volunteers/#{volunteer.id}", params: params 
        expect(resonse).to have_http_status :unproccessable_entity
      end

      it "does not update the volunteer" do
        patch "/api/v1/volunteers/#{volunteer.id}", params: params 
        expect { volunteer.reload }.to_not change(volunteer, :name).to("") 
      end
    end

    context "with valid parameters" do
      # some set of failing parameters
      let(:params) do
        {
          volunteer:  {
            image_url: "new.jpg", 
            name: "test1", 
            job_desc: "description"
          }
        }
      end

      it "returns no content" do
        patch "/api/v1/volunteers/#{volunteer.id}", params: params 
        expect(resonse).to have_http_status :no_content
      end

      it "updates the volunteer" do
        patch "/api/v1/volunteers/#{volunteer.id}", params: params 
        expect { volunteer.reload }.to change(volunteer, :image_url)
        .to("new.jpg") 
      end
    end
  end
end

Upvotes: 2

Related Questions