還記得一開始提到的 unit test,我們希望著重在小的功能上進行測試,但一個 App 常常牽扯到各式各樣不同的關聯。
這時候的 unit test 就會變得很難寫,因為你需要顧慮到許多不同的方法所回傳的值,因為他們都會影響到你測試的結果!
這時候 mock 系列的功能就可以很有效地幫助到你,因為他就是為了假裝、為了模仿而生!
Double method
從概念上來說,double 是一個製造假物件的方法,而這個假物件進而能夠接受方法,設定好回傳值。
我們先看看範例,再來解釋:
Double method
ruby
1 2 3 4 5 6
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James", dunk: "Ah!!!!", shoot: "Goal!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
end
end
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James")
allow(basketball_player).to receive(:dunk).and_return("Ah!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
end
end
RSpec.describe "double method" do
it "can defined method to be invoked" do
basketball_player = double("Lebron James")
allow(basketball_player).to receive_messages(dunk: "Ah!!!!", shoot: "Goal!!!!")
expect(basketball_player.dunk).to eq("Ah!!!!")
expect(basketball_player.shoot).to eq("Goal!!!!")
end
end
class Cowboy
def initialize(name)
@name = name
end
def fighting?
true
end
def draw_the_gun
"Bang!!!"
end
def be_shot
"Help me..."
end
def continue?
false
end
end
class Bar
attr_reader :cow_boy
def initialize(cow_boy)
@cow_boy = cow_boy
end
def start_fighting
if cow_boy.fighting?
cow_boy.draw_the_gun
cow_boy.be_shot
cow_boy.continue?
end
end
end
RSpec.describe Bar do
let(:cow_boy) { double("Gene Autry", fighting?: true, draw_the_gun: "Bang!!!", be_shot: "Help me...", continue?: true) }
subject { described_class.new(cow_boy) }
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
expect(cow_boy).to receive(:fighting?)
expect(cow_boy).to receive(:draw_the_gun)
expect(cow_boy).to receive(:be_shot)
expect(cow_boy).to receive(:continue?)
subject.start_fighting
end
end
end
這是會成功通過的測試,因為我比較懶一點直接寫在初始化裡面 請見諒~
然後我們在測試中期待這個物件會接收到這些方法~
但如果我們不給予這些方法的話,這個方法是會沒辦法成功運作的喔!
因為我們把 double 物件放進去 Bar 類別生成實體,進而執行他的 start_fighting 這個方法,其中有使用到的方法都會先去參考 double 物件有沒有才繼續向下執行!
可以看看如果我們只寫了 double 物件的話:
use double
ruby
1 2 3 4 5 6 7 8 9 10
RSpec.describe Bar do
let(:cow_boy) { double("Gene Autry") }
subject { described_class.new(cow_boy) }
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
subject.start_fighting
end
end
end
再看看執行的 Output !
程式的執行在 if cow_boy.fighting? 中斷了,內容也有提到我們的 double 物件沒有接受到這個方法~
所以我們可以用最上面介紹的三種寫法來填入方法,使我們可以專注在測試 Bar 的方法,而不受到 cow_boy 干擾!
而如果想要指定接收次數的話,也可以加入計數的方法喔!
畢竟某些時候你會想要限制有些方法只執行一次,或是最多兩次等等的條件~
limit times
ruby
1 2 3 4 5 6 7 8 9 10 11
describe "#start_fighting method" do
it "expect cow_boy start_fight" do
expect(cow_boy).to receive(:fighting?)
expect(cow_boy).to receive(:draw_the_gun)
expect(cow_boy).to receive(:be_shot).twice
#expect(cow_boy).to receive(:be_shot).exactly(1).times
#expect(cow_boy).to receive(:be_shot).at_most(1).times
expect(cow_boy).to receive(:continue?)
subject.start_fighting
end
end
上面有註解的部分,都是可以用這樣的寫法!
現在我們加上了 twice 就是希望這個方法能被執行兩次,那我們先來看看 Output
這邊的意思就是,我們期待會收到兩次的執行,但其實程式碼只有一次!
那我們先改寫要測的的方法,讓牛仔中槍兩次吧
change test
ruby
1 2 3 4 5 6 7 8
def start_fighting
if cow_boy.fighting?
cow_boy.draw_the_gun
cow_boy.be_shot
cow_boy.be_shot
cow_boy.continue?
end
end