Controller Spec

Rails 5 移除了 Controller 測試!,但可以使用這個 rails-controller-testing RubyGem 繼續沿用。

關於 Rails Controller 請參考這篇文章 Action Controller Overview

命名慣例

文件擺放位置:

spec/controllers/application_controller.rb

一樣按照 Controller 的慣例複數_controller

spec/controllers/users_controller.rb

若是 Admin::UsersController,則:

spec/controllers/admin/users_controller.rb

若文件按照命名慣例,放在 spec/controllers 下,RSpec 就會自動幫我們推測出,這個檔案是一個 Controller spec。會自動幫我們加上 type: :controller 這個 metadata 到我們第一層的 describe 區塊。

原本要寫成這樣:

RSpec.describe UsersController, type: :controller do

end

現在放對位置只要寫成

RSpec.describe UsersController do

end

即可!

RESTful actions

七個 RESTful actions 按順序通常是:index, new, create, show, edit, update, destroy

通常按正常流程實作一套 CRUD 便會是這個順序。

Controller 通常會測 response 是否成功,便可以知道該頁可不可以存取。測試 Redirect 則可以了解,譬如使用者刷卡後,是否成功跳轉到我們想要使用者去的頁面。測試 HTTP 狀態,則可以確定出錯時,是不是返回正確的 HTTP 狀態碼。再進一步也可以測試該頁的 Flash 是否如預期般顯示。

HTTP 動詞

有五個要了解的動詞,GET、POST、PUT、PATCH、DELETE,HTTP 動詞通常都是採取全大寫的寫法。

RESTful 的七個 actions,有些 path 是重複的,唯一區別就在於 HTTP 動詞不一樣。

譬如一個 PhotosControllerindexcreate action,他們的目標路徑是一樣的,都是 /photos。不一樣的點在於,index 用 GET,而 create 用 POST。

  • 獲得資料:GET
  • 發送資料:POST
  • 更新資料:PUT、PATCH
  • 刪除資料:DELETE

不太了解可以再閱讀一次 Rails Routing from the Outside In再複習一下。

使用 HTTP 動詞我們說「請求」,伺服器處理完會回復「響應」給我們。

  • request 請求
  • response 響應

Controller 要測什麼

測試 Controller 的所有功能都是由 Rails 內建的 ActionController::TestCase::Behavior 這個類別所提供,RSpec 對 ActionController::TestCase::Behavior 進行了包裝,提供我們幾個好用的方法與物件:

  • response 代表「響應」的物件

response,中文稱響應,是屬於 ActionController::TestResponse 類別的物件,代表上一次的 HTTP 響應。譬如在 Controller 測試裡,撰寫 get :index,便可以向 Controller 的 index action 發送 GET 請求:

class UsersController < ApplicationController
  def index
  end
end

RSpec.describe UsersController do
  describe "#index" do
    it "response is success" do
      get :index

      expect(response.success?).to eq true
    end
  end
end

response 有一個實體方法 success?,HTTP 請求成功響應(狀態碼為 20x),則為 true,反之為 false

expect(response.success?).to eq true

通常會寫成這樣:

expect(response).to be_success

這是 RSpec 的 一種慣用寫法。

  • render_template 模版的算繪
  • redirect_to 轉址
  • have_http_status HTTP 狀態
RSpec.describe UsersController do
  describe "#index" do
    it "response is success" do
      get :index

      expect(response).to render_template(:index)
      expect(response).to have_http_status(200)
    end
  end
end

多種不同的發請求形式

那往 create 發 POST 呢?

post :create

就是往 Controllercreate action 發出 POST 請求。也可以用字串:

post "create"

但是寫 Symbol 可以少打一個字元,也不會配置額外的記憶體!

也可以指定往一個路徑發送特定 HTTP 動詞的請求:

post "/photos"

或是使用路由的幫助方法(Routing Helpers):

post photos_path

相對路徑

post photos_url

或是絕對路徑都可以!

若是要向指定路徑發送請求,有時候需求變了,路由會一併做更改,所以用路由的幫助方法(Routing Helpers)來引用路徑比較好,這樣路由變了也不用回來修改測試發送的路徑。

測試 Response

expect(response).to be_success
expect(response).not_to be_success

測試 HTTP 狀態

expect(response).to have_http_status(:unauthorized)

狀態列表

100    :continue
101    :switching_protocols
102    :processing

200    :ok
201    :created
202    :accepted
203    :non_authoritative_information
204    :no_content
205    :reset_content
206    :partial_content
207    :multi_status
208    :already_reported
226    :im_used

300    :multiple_choices
301    :moved_permanently
302    :found
303    :see_other
304    :not_modified
305    :use_proxy
307    :temporary_redirect
308    :permanent_redirect

400    :bad_request
401    :unauthorized
402    :payment_required
403    :forbidden
404    :not_found
405    :method_not_allowed
406    :not_acceptable
407    :proxy_authentication_required
408    :request_timeout
409    :conflict
410    :gone
411    :length_required
412    :precondition_failed
413    :payload_too_large
414    :uri_too_long
415    :unsupported_media_type
416    :range_not_satisfiable
417    :expectation_failed
422    :unprocessable_entity
423    :locked
424    :failed_dependency
426    :upgrade_required
428    :precondition_required
429    :too_many_requests
431    :request_header_fields_too_large

500    :internal_server_error
501    :not_implemented
502    :bad_gateway
503    :service_unavailable
504    :gateway_timeout
505    :http_version_not_supported
506    :variant_also_negotiates
507    :insufficient_storage
508    :loop_detected
510    :not_extended
511    :network_authentication_required

Layouts and Rendering in Rails 可以找到。

測試 Redirect

expect(response).to redirect_to(home_path)

測試 Flash

expect(flash[:alert]).to eq "content you expected"

綜合範例:

class SubscriptionsController < ApplicationController
  before_action :authenticate_user

  def create
    @subscription = Subscription.new(model_params)

    if @subscription.save
      redirect_to subscription_path(@subscription), notice: I18n.t("flash.subscription_succeeded")
    else
      redirect_to root_path, notice: I18n.t("flash.subscription_failed")
    end
  end
end
RSpec.describe SubscriptionsController do
  describe "POST #create" do
    context "success" do
      it "redirects and flash" do
        user = create(:user)
        sign_in(user)

        do_request

        expect(user.subscription).to be_present
        expect(response).to redirect_to subscription_path
        expect(flash[:alert]).to eq I18n.t("flash.subscription_succeeded")
      end
    end

    context "failed" do
      it "redirects and flash" do
        sign_in create(:user, :with_subscription)

        do_request

        expect(response).to redirect_to root_path
        expect(flash[:alert]).to eq I18n.t("flash.subscription_failed")
      end
    end
  end
end

Shoulda Matchers 提供跟 Controller 相關的方法

https://github.com/thoughtbot/shoulda-matchers#actioncontroller-matchers

延伸閱讀

Rails 關於測試 Controller 的文章:Functional Tests for Your Controllers

results matching ""

    No results matching ""