---
# System prepended metadata

title: RSpec

---

# RSpec
### Using Test Doubles as Mocks and Stubs

* SUT (System Under Test) 待側系統
* DOC (depended-on component) 依賴元件

#### Test Doubles
即為測試替身，一個系統流程包括資料的傳輸(params)以及系統的架設(action)，而在整個程式中往往運作流程繁雜(A→B→C→D.....)，所以並不會建構出完整流程來進行測試，因此需要一個“替身”來減少複雜度，並且專注於特定部分的測試。
![](https://i.imgur.com/33QEBGk.gif)

```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屬於消極方法**)
![](https://i.imgur.com/EW5kOdE.gif)

```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屬於積極方法**)
![](https://i.imgur.com/TOqoj7g.gif)

```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概念相像但會驗證方法有沒有被使用到
![](https://i.imgur.com/5Nm1zB0.gif)

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