# RSpec
### Using Test Doubles as Mocks and Stubs
* SUT (System Under Test) 待側系統
* DOC (depended-on component) 依賴元件
#### Test Doubles
即為測試替身,一個系統流程包括資料的傳輸(params)以及系統的架設(action),而在整個程式中往往運作流程繁雜(A→B→C→D.....),所以並不會建構出完整流程來進行測試,因此需要一個“替身”來減少複雜度,並且專注於特定部分的測試。

```ruby=
RSpec.describe 'double' do
let(:store) { double('store', sell: 'earn the money!')}
it 'should invoke method first' do
expect(store.sell).to eq('earn the money!')
end
end
```
#### Stubs
可以回傳預先設定值的假物件,但並不會實際呼叫方法(**stub屬於消極方法**)

```ruby=
class NumberGenerator
def random
"A"rand(1..10)
end
end
RSpec.describe "Random" do
it "generates a random number" do
generator = NumberGeneraor.new
allow(generator).to receive(:rand).and_return(5) #stub
expect(generator.random).to eq("AAAAA")
end
end
```
##### 使用時機:
1. 必須獲得資料才可以繼續進行→中斷測試
2. 獲得資料過程中運行過久(撈取資料龐大)→影響效率
```ruby=
#對find使用stub以避免向資料庫進行操作
it "stubs the class" do
allow(project).to receive(:find).and_return(Project.new(name: "Project Greenlight"))
project = Project.find(1)
expect(project.name).to eq("Project Greenlight")
end
```
3. 觀察異常(要求回應無效結果)
#### Mocks
除了回傳預定值以外,也會對待測系統內部流程驗證與實際測試狀況呼叫與否(**mock屬於積極方法**)

```ruby=
#必須呼叫以期望的方法
it "expects stuff" do
mocky = double("Mock")
expect(mocky).to receive(:name).and_return("Paul")
expect(mocky).to receive(:weight).and_return(100)
expect(mocky.name).to eq("Paul")
end
```
```ruby=
#待側系統中必須含有測試方法
class ImageFlipper
end
RSpec.describe "ImageFlipper" do
it "flips an image" do
processor = double("processor")
expect(processor).to receive(:flip).with("image.jpg")
im = ImageFlipper.new(processor)
im.flip("image.jpg")
end
end
end
```
```ruby=
#修改後
class ImageFlipper
def initilize(processor)
@processor = processor
end
def flip(flie)
@processor.flip(file)
end
end
```
#### Instance Doubles
如果使用double時,待測系統內部程式碼的更動,並不會影響測試結果通過與否,而使用Instance Doubles時,可以對於方法是否存在進行驗證
```ruby=
class Image
#沒有寫方法
end
RSpec.describe Image do
it "#take_picture"
Image = double(take_picture: "ok!")
expect(Image.take_picture).to eq("ok!") #測試失敗,因為沒有take_picture這個方法
end
end
```
```ruby=
class Image
def take_picture #寫入方法
"ok!"
end
end
RSpec.describe Image do
it "#take_picture"
Image = instance_double(Image, take_picture: "ok!")
expect(Image.take_picture).to eq("ok!") #通過測試
end
end
```
#### Spies
跟 test double概念相像但會驗證方法有沒有被使用到

```ruby=
RSpec.describe 'spies' do
let(:burger) { spy('burger') }
it 'confirm a message has been received' do
burger.eat
expect(burger).to have_received(:eat)
end
end
```
比較test double寫法順序
```ruby=
RSpec.describe 'double' do
let(:store) { double('store', sell: 'earn the money!')}
it 'should invoke method first' do
expect(store.sell).to eq('earn the money!')
end
end
```
---
#### 參考
>https://ithelp.ithome.com.tw/users/20140124/ironman
>http://xunitpatterns.com/Test%20Double.html
>https://juejin.cn/post/6968624352191315975
>https://partypeopleland.github.io/artblog/2020/09/08/what-is-stub-and-mock/
factory stub mock使用時機
instance double是否需要包含class?