--- title: Rspec Request/Controller spec description: 到底該用哪個 robots: noindex, nofollow lang: zh-tw dir: ltr breaks: true tags: Rspec, Request, Controller disqus: hackmd --- 參考來源有以下: [rails-controller-testing](https://github.com/rails/rails-controller-testing) [Deprecate assigns() and assert_template in controller testing](https://github.com/rails/rails/issues/18950) [Deprecate ActionController::TestCase in favor of ActionDispatch::IntegrationTest ](https://github.com/rails/rails/issues/22496) [rspec relish-request spec](https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec) [各個spec該如何設定host](https://stackoverflow.com/questions/598933/how-do-i-change-the-default-www-example-com-domain-for-testing-in-rails) [request spec 教學1](https://dev.to/dtfocus/testing-with-rspec-a-quick-look-into-request-spec-38en) [rspec github](https://github.com/rspec/rspec-rails) [undefined-method-env-for-nilnilclass](https://stackoverflow.com/questions/27284657/undefined-method-env-for-nilnilclass-in-setup-controller-for-warden-error) [看這篇](https://medium.com/swlh/differences-between-expect-and-expect-in-rspec-ae5b5b62886) # Request Spec VS Controller Spec 在 Rails3.5 時釋放可以開始使用 Controller spec 但是到了 Rails 5 DHH 開始移除了 `assigns`、`assert_templete` > Testing what instance variables are set by your controller is a bad idea. That's grossly overstepping the boundaries of what the test should know about. You can test what cookies are set, what HTTP code is returned, how the view looks, or what mutations happened to the DB, but testing the innards of the controller is just not a good idea. > The same goes for assert_template. This ties the test to the internal structure of how your views are organized. That's not what the test should be testing. It should be testing what DOM elements it expect to be there. Rearranging your templates and partials should not cause these tests to break. Deprecate and recommend using assert_select instead. 這邊也說到他已經移除了`ActionController::TestCase` > With the speed increase we achieved for ActionDispatch::IntegrationTest in Rails 5, there's no longer a need to also have ActionController::TestCase around. We're changing the scaffolds in #22076 but we should also get the message out that ActionController::TestCase is deprecated and will be made into a gem from Rails 5.1. 個人理解是鼓勵工程師將 Controller 測試改成 Request 測試 所以他才在 Rails5 的時候提升 request spec 的速度 所以你問我到底該選哪個? ==Rails3.5~4 => controller spec== ==Rails5 up => request spec== 因為在 rails5 之前 request 速度確實比 controller 還要慢 在我們自己的專案上 blog test controller spec ![](https://i.imgur.com/ydHfqdz.png) 但若改成request spec ![](https://i.imgur.com/ib3RleO.png) 但在rails5以上(這邊還沒有測試過所以要補上 ## 一些rspec的用法 ### login_user rspec 如果不想要每次都一直`sign_in @user` 還有設定一些條件 其實可以做成一個 module 方便在測試的時候不用一直登入而且可以在裡面做其他更多的預設條件 如果想要有`sign_in`的方法使用需要先在`spec_heloper`加上 ```ruby= config.include Devise::TestHelpers, type: :controller # controller spec才需要加 config.include Devise::Test::IntegrationHelpers, type: :request # request spec才需要加 ``` 然後在`/spec/support`下新增`ControllerMacros.rb` ```ruby= # ControllerMacros.rb module ControllerMacros # possible roles: :user, :admin, :pos_user def login_user(role = nil, type: :each, fat_shop: true, shop_traits: []) before(type) do if fat_shop @user = create(role || :user) @shop = @user.shop else @shop = create :shop, *[shop_traits].flatten @user = create(role || :user_admin, shop: @shop) end end before(:each) do @request.host = @shop.domains.first.host @request.env['devise.mapping'] = Devise.mappings[:user] sign_in @user end end end ``` 最後再把這個檔案 extends 到`spec_helper`即可 ```ruby= config.extend ControllerMacros, type: :controller #request spec要改成type: :request ``` #### 遇到的雷 再將controller改成request的時候發現 `Devise::TestHelpers, type: :controller`不能只把`type`改成`request` 可能會出現錯誤 `undefined method env for nilnilclass` 在 rpsec relish 寫到 > Request specs are RSpec wrappers for Rails integration tests. 才知道request spec可能被歸類為 integration test 所以要include的helper應該是`Devise::Test::IntegrationHelpers, type: :request` 只要增加這行就沒問題了 ### expect ()和 {} 的差別 [看這篇](https://medium.com/swlh/differences-between-expect-and-expect-in-rspec-ae5b5b62886) 簡單來說expect(X).to eq(X) 會直接把()內的值拿出來然後直接比對 但expect{X}.to raise_error(X) 是會先把觀察block裡的所有行程直到做完如果中間有遇到任何情況符合後面的expect值再回傳