---
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

但若改成request spec

但在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值再回傳