蒼時弦也
蒼時弦也
資深軟體工程師
發表於

優雅的 RSpec 測試 - 共用案例

在我們在撰寫測試的過程中,可能因為同一類型的物件行為有著類似的情況,因此會需要重複的專寫相似的測試案例,這個時候我們就可以利用「共用案例(Shared Example)」的機制來重複利用這些測試案例。

使用方式

要定義一個共用案例,可以使用 .shared_examples 來進行,因為是 Class Method(類別方法)因此可以直接在測試中撰寫,或者另外新增一個檔案來進行定義。

 1RSpec.describe 'User API', type: :api do
 2  shared_examples 'successful response' do
 3    it { is_expected.to be_successful }
 4  end
 5
 6  describe 'GET /users' do
 7    # ...
 8    it_behaves_like 'successful response'
 9  end
10
11  describe 'GET /users/:id' do
12    # ...
13    it_behaves_like 'successful response'
14  end
15end

像這樣子我們就可以在所有預期會有「成功回傳」的測試中重複使用這些共用的測試案例,要注意的是使用共用案例有以下限制。

範圍

根據我們定義共用案例時機,會讓可以引用的範圍受到不同程度的限制。基本原則是「下層可以繼承上層」的方式。

舉例來說,如果是跨 Context(情境)的狀況,是無法被共用的。

 1RSpec.describe 'User API', type: :api do
 2  describe 'GET /users' do
 3    shared_examples 'user list' do
 4      it { is_expected.to include('users' => be_a(Array)) }
 5    end
 6    # ...
 7
 8  end
 9
10  describe 'GET /admin/users' do
11    # ...
12    it_behaves_like 'user list'
13  end
14end

因為 GET /usersGET /admin/users 是相同層級,因此會無法飲用到另一個層級的共用案例,要解決這個問題只需要向上放置即可。

也因此,如果是經常被共用的案例,我們可以直接對整個 RSpec 進行定義。

1RSpec.shared_examples 'user list' do
2  it { is_expected.to include('users' => be_a(Array)) }
3end

如此一來,我們只需要在運行測試前載入這個共用案例的定義就可以使用,而不需要在不同的測試案例中去定義。

參數

我們在前面的例子中定義了 user list 這個共用案例,但大多數 API 回應都是列表,我們可以利用參數的機制讓他變成 list resources 之類的共用案例。

1RSpec.shared_examples 'list resources' do |resource:|
2  it { is_expected.to include(resource => be_a(Array)) }
3end

在使用時,我們就可以額外的提供參數來使用。

 1RSpec.describe 'User API', type: :api do
 2  describe 'GET /users' do
 3    # ...
 4    it_behaves_like 'list resources', resource: 'users'
 5  end
 6
 7  describe 'GET /admin/users' do
 8    # ...
 9    it_behaves_like 'list resources', resource: 'admin_users'
10  end
11end

像這樣,我們就可以提供不同類型的 List API 來使用這個共用案例,而不需要重複定義多次。

除此之外,我們也可以傳入一個 Block 在共用案例被執行之前呼叫,來做一些對應的處理。

 1RSpec.describe 'User API', type: :api do
 2  describe 'GET /users' do
 3    # ...
 4    it_behaves_like 'list resources', resource: 'users' do
 5      before { API.adapter = MockUserAPI }
 6    end
 7  end
 8
 9  describe 'GET /admin/users' do
10    # ...
11    it_behaves_like 'list resources', resource: 'admin_users' do
12	  before { API.adapter = MockAdminUserAPI }
13    end
14  end
15end

如此一來我們在共用案例上就能有非常多的彈性,來對應那類有著「類似行為」的測試。下一篇我們會介紹「自訂匹配器」並且分析跟共用案例的差異。


如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用優雅的 RSpec 測試回饋表單告訴我。