我們已經瞭解到該如何利用 Allow 來改變一個物件的回傳,然而在我們使用 Allow 的時候還可能會遇到需要呼叫多次、搭配 Block 使用的情況,在這樣的狀況下該如何處理呢?
Each 的替身
在 Ruby 中如果是一個看起來像是陣列可以列舉(Enumerable)的物件,我們會很自然的使用 #each
方法來處理,然而當我們希望被呼叫的對象回傳不同的內容,是否有辦法製作對應的替身呢?
假設我們有一個叫做 MovieList 的物件引用了 Enumerable
模組,並且實作了 #each
方法可以從 API 回應中取出一些物件。
1class MovieList
2 include Enumerable
3 # ...
4 def each(&block)
5 @api.list.map { |item| Movie.new(item) }.each(&block)
6 end
7end
當我們測試時,除了可以將 @api
以依賴注入的方式替換掉來製作替身,但這樣如果是在一個依賴 MovieList 的物件測試時就很難處理,我們可以對 #each
方法製作替身,來讓我們不用定義複雜的依賴。
1RSpec.describe MovieListSerializer do
2 subject(:serializer) { MovieListSerializer.new(list) }
3
4 let(:list) { MovieList.new } # API may initialized inside MovieList
5
6 before do
7 allow(list).to receive(:each).and_yield(Movie.new)
8 end
9
10 describe '#to_a' do
11 subject { serializer.to_a }
12
13 it { is_expected.to include(be_a(Movie)) }
14 end
15end
如此一來,我們就可以改變 #each
的行為,或者任何需要我們需要 Block 呼叫的情況。
不同回傳
延續前面的例子,假設我們需要測試「多個回傳」該怎麼辦呢?如果只使用一次 #and_yield
的話是無法回傳不同物件的,也因此我們還可以多次呼叫 #and_yield
來傳回多個物件。
1RSpec.describe MovieListSerializer do
2 subject(:serializer) { MovieListSerializer.new(list) }
3
4 let(:list) { MovieList.new } # API may initialized inside MovieList
5
6 before do
7 allow(list).to receive(:each).and_yield(Movie.new(id: 1))
8 .and_yield(Movie.new(id: 2))
9 end
10
11 describe '#to_a' do
12 subject { serializer.to_a }
13
14 it { is_expected.to include(have_attributes(id: 1)) }
15 it { is_expected.to include(have_attributes(id: 2)) }
16 end
17end
如果是普通的方法回傳,我們也可以透過告訴 #and_return
來提供複數的回傳狀況。
1RSpec.describe MovieListSerializer do
2 subject(:serializer) { MovieListSerializer.new(list) }
3
4 let(:list) { MovieList.new } # API may initialized inside MovieList
5
6 before do
7 allow(list).to receive(:empty?).and_return(false, true, true)
8 end
9
10 describe '#next_json' do
11 subject { serializer.next_json }
12
13 it { is_expected.not_to be_nil? }
14
15 context 'when movie list is empty' do
16 before { 2.times { serializer.next_json } }
17
18 it { is_expected.to be_nil? }
19 end
20 end
21end
像這樣給 #and_return
多個結果,那麼 RSpec 就會在每次呼叫時回傳下一個,直到沒有可以再繼續回傳為止。
利用這樣的方法,我們就可以刻意的控制像是計數器之類的方法回傳我們期待的數值,來針對特定情況應用。
如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用優雅的 RSpec 測試回饋表單告訴我。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 優雅的 RSpec 測試 - 前言
- 優雅的 RSpec 測試 - 撰寫測試的方式
- 優雅的 RSpec 測試 - RSpec 概觀
- 優雅的 RSpec 測試 - 測試案例
- 優雅的 RSpec 測試 - 組織測試
- 優雅的 RSpec 測試 - 前置處理
- 優雅的 RSpec 測試 - 常見匹配器
- 優雅的 RSpec 測試 - 內容匹配
- 優雅的 RSpec 測試 - 錯誤匹配
- 優雅的 RSpec 測試 - 共用案例
- 優雅的 RSpec 測試 - 自訂匹配器
- 優雅的 RSpec 測試 - 輔助方法
- 優雅的 RSpec 測試 - 測試替身
- 優雅的 RSpec 測試 - Mock 與 Stub
- 優雅的 RSpec 測試 - Allow 的使用方式
- 優雅的 RSpec 測試 - Allow 變化應用
- 優雅的 RSpec 測試 - Spy 的應用
- 優雅的 RSpec 測試 - 物件的可測試性
- 優雅的 RSpec 測試 - 耦合與依賴注入
- 優雅的 RSpec 測試 - 探索式的測試與重構