---
title: "優雅的 RSpec 測試 - 常見匹配器"
date: 2023-02-17T00:00:00+08:00
publishDate: 2023-02-17T00:00:00Z
lastmod: 2023-09-03T14:30:07+08:00
tags: ["RSpec","教學","測試","匹配","Matcher"]
series: "elegant-rspec"
toc: true
permalink: "https://blog.aotoki.me/posts/2023/02/17/elegant-rspec-common-matcher/"
language: "zh-tw"
---


經過測試案例、組織測試、前置處理等等概念後，我們現在已經掌握了對於如何規劃一個「容易理解」的測試有所概念，接下來我們要針對 RSpec 的斷言機制中，強大的「匹配器（Matcher）」來深入討論。

<!--more-->

## Equal

Equal（相等）是最為常見的匹配器，在其他測試框架中也可以算是標準配備，在 RSpec 中我們可以使用 `eq` 的縮寫來使用這個匹配器。

```ruby
RSpec.describe Calculator do
  subject(:calculator) { Calculator.new }

  describe '#add' do
    subject { calculator.add(1, 1) }

    it { is_expected.to eq(2) }
  end
end
```

以上面的例子來說，我們呼叫了 `Calculator#add` 這個方法，並且預期回傳的數值是 `2` 就可以使用 `eq` 來檢查。

假設是在其他測試框架，通常會使用 `assert`（斷言）關鍵字，並且直接確認是否是 `true` 來進行判斷，類似這樣。

```ruby
# ...
assert calculator.add(1, 1) == 2
# ...
```

RSpec 加入了匹配器的設計，讓我們可以用 `is expected to equal 2` 這樣接近人類可以理解的句子去撰寫測試，進而讓我們的測試案例更加容易閱讀。

## Be

Be 是一個特殊類型的匹配器，我們可以用任何 `be_` 作為開頭來進行布林類型的匹配。大多數人用過像是 `be_truthy`、`be_falsy`、`be_nil` 這三個比較常見的 Be 匹配器，然而 `be_` 是適用所有布林方法。

我們以 [Rack::Response](https://www.rubydoc.info/gems/rack/Rack/Response) 為例子，假設我們在處理了一個請求後希望得到成功的回傳，那們就可以用 Be 匹配器來處理。

```ruby
RSpec.describe SimpleController do
  subject(:controller) { SimpleController.new }

  describe '#index' do
    subject { controller.index }

    it { is_expected.to be_successful }
  end
end
```

我們之所以可以這樣使用，是因為 Rack::Response 物件包含了 `#successful?` 這個方法，因此 RSpec 會自動嘗試呼叫這個方法來進行比對。

也因此，我們在設計物件時，如果是回傳「布林值」類型的方法都應該使用 `?` 結尾，這類似 Ruby 語言的一個慣例，也能讓其他人預期呼叫後回傳的型別。

> 另一個好處就是我們不用另外使用 `describe` 建立新的測試群組，就可以針對這類型的方法進行測試。

## Change

Change（改變）匹配器也是我們在測試中很常使用的一個匹配器，我們可以用來確認呼叫「前後」的改變。

```ruby
RSpec.describe Order do
  subject(:order) { Order.new }

  describe '#add_item' do
    subject(:add_item) { order.add_item(OrderItem.new(amount: 100)) }

    it { expect { add_item }.to change(order, :subtotal).from(0).to(100) }
    it { expect { add_item }.to change(order, :subtotal).to(100) }
    it { expect { add_item }.to change(order, :subtotal).by(100) }
  end
end
```

上面的範例展示了當我們呼叫 `Order#add_item` 前後的變化，根據使用情境我們可以使用 `from` 跟 `to` 的組合，明確的指定從 0 變成 100 或者只使用 `to` 去表示「最終狀態」在一些情況下，我們可以用 `by` 來確認「變化量」像是 `+1` 或者 `-1` 這樣的情境。

Change 的使用也不限於數值，像是字串也可以用 `from` 和 `to` 來進行確認，唯一的限制是無法用 `not_to` 的條件使用，因此要檢查「減少」的情境可以用 `by(-1)` 而不是 `not_to` 搭配 `by(1)` 的方式。

---

如果想在第一時間收到更新，歡迎[訂閱弦而時習之](https://mailchi.mp/aotoki/graceful-rspec)在這系列文章更新時收到通知，如果有希望了解的知識，可以利用[優雅的 RSpec 測試回饋表單](https://us4.list-manage.com/survey?u=dd3d68032c0510041f1302539&id=5ddf86cae1&attribution=false)告訴我。

