RSpec 3 的重大變更

Myron Marston

2014年5月21日

更新:現在有提供此文章的日文翻譯

RSpec 3.0.0 RC1 已在幾天前發布,而 3.0.0 的最終版本也即將推出。我們已經使用了 beta 版 6 個月了,並且很興奮能與您分享它們。以下是新功能:

所有 gem 的變更

移除對 Ruby 1.8.6 和 1.9.1 的支援

這些 Ruby 版本早已終止生命週期,RSpec 3 不支援它們。

改進對 Ruby 2.x 的支援

最近發布的 RSpec 2.x 版本(即在 Ruby 2.0 發布之後的版本)已正式支援 Ruby 2,但 RSpec 3 的支援已大幅改進。我們現在支援使用 Ruby 2 的新功能,例如關鍵字引數和前置模組。

新的 rspec-support gem

rspec-support 是一個新的 gem,我們將它用於 rspec-(core|expectations|mocks|rails) 中多個 gem 所需的通用程式碼。它目前不包含任何供終端使用者或擴充函式庫作者使用的公開 API,但我們未來可能會公開它的一些 API。

如果您透過在 Gemfile 中從 github 取得來源來執行最前沿的 RSpec,您也需要開始對 rspec-support 執行相同的操作。

穩健且經過良好測試的升級流程

RSpec 3 中的每個重大變更在 2.99 版中都有對應的棄用警告。在整個 beta 版中,我們進行了許多升級,以確保此流程盡可能順利。我們整理了逐步升級說明

升級流程還強調了 RSpec 新的棄用系統,該系統具有高度可配置性(允許您將棄用輸出到檔案或將所有棄用轉換為錯誤),並且旨在最大限度地減少重複的棄用輸出。

改進的文件

我們投入大量精力來更新所有 gem 的 API 文件。它們目前託管在 rubydoc.info 上。

…但我們目前正在更新 rspec.info 來自行託管它們。

雖然文件仍在進行中(坦白說,永遠都會如此),但我們已確保將所有公開 API 明確聲明為 SemVer 合規的一部分。我們絕對致力於在所有 3.x 版本中維護所有公開 API。另一方面,私有 API 被標記為私有,因為我們特別希望保留在任何 3.x 版本中隨意變更它們的彈性。

請勿使用我們宣告為私有的 API。如果您發現現有的公開 API 無法滿足您的需求,請提出詢問。我們很樂意為了您的需求而將私有 API 公開,或新增一個新的 API 來滿足您的使用案例。

gem 現在已簽署

我們已開始簽署我們的 gem 發行版本。雖然目前的 gem 簽署系統遠非理想,並且正在開發更好的解決方案,但總比沒有好。我們已將我們的公開憑證放在 GitHub 上。

如需目前 gem 簽署系統的更多詳細資訊,請參閱使用簽署 Ruby Gem 的實用指南

零猴子修補模式

現在可以使用 RSpec,而無需任何猴子修補。這項工作的大部分基礎是在最近的 2.x 版本中奠定的,這些版本將新的 expect 型語法新增到 rspec-expectations 和 rspec-mocks 中。我們在 RSpec 3 中完成了其餘工作,並為其餘的猴子修補提供了替代方案。

為了方便起見,您可以使用一個選項停用所有猴子修補

# spec/spec_helper.rb
RSpec.configure do |c|
  c.disable_monkey_patching!
end

感謝 Alexey Fedorov 實作此設定選項。

更多資訊

rspec-core

hook 範圍的新名稱::example:context

RSpec 2.x 有三個不同的 hook 範圍

describe MyClass do
  before(:each) { } # runs before each example in this group
  before(:all)  { } # runs once before the first example in this group
end
# spec/spec_helper.rb
RSpec.configure do |c|
  c.before(:each)  { } # runs before each example in the entire test suite
  c.before(:all)   { } # runs before the first example of each top-level group
  c.before(:suite) { } # runs once after all spec files have been loaded, before the first spec runs
end

有時,使用者對 :each:all 的含義表示困惑,特別是當您在設定區塊中使用 :all 時,可能會感到困惑

# spec/spec_helper.rb
RSpec.configure do |c|
  c.before(:all) { }
end

在此內容中,術語 :all 表示此 hook 將在套件中的所有範例之前執行一次 — 但這才是 :suite 的用途。

在 RSpec 3 中,:each:all 有別名,使其範圍更加明確::example:each 的別名,而 :context:all 的別名。請注意,:each:all 並「不」棄用,我們也無意這麼做。

感謝 John Feminella 實作此功能。

更多資訊

DSL 方法將範例作為引數傳遞

RSpec::Core::Example 提供對範例所有詳細資訊的存取權:其描述、位置、中繼資料、執行結果等。在 RSpec 2.x 中,範例是透過 example 方法公開的,該方法可以從任何 hook 或個別範例存取

describe MyClass do
  before(:each) { puts example.metadata }
end

在 RSpec 3 中,我們已移除 example 方法。相反地,範例執行個體會以明確引數的形式傳遞至所有範例範圍的 DSL 方法

describe MyClass do
  before(:example) { |ex| puts ex.metadata }
  let(:example_description) { |ex| ex.description }

  it 'accesses the example' do |ex|
    # use ex
  end
end

感謝 David Chelimsky 提出這個想法,並感謝他實作此功能!

更多資訊

新的 expose_dsl_globally 設定選項可停用 rspec-core 猴子修補

RSpec 2.x 猴子修補了 mainModule,以提供頂層方法,例如 describeshared_examples_forshared_context

shared_examples_for "something" do
end

module MyGem
  describe SomeClass do
    it_behaves_like "something"
  end
end

在 RSpec 3 中,這些方法現在也可以在 RSpec 模組上使用(除了仍然可以作為猴子修補使用)

RSpec.shared_examples_for "something" do
end

module MyGem
  RSpec.describe SomeClass do
    it_behaves_like "something"
  end
end

您可以將新的 expose_dsl_globally 設定選項設定為 false,以完全移除 rspec-core 的猴子修補(這會使上面的第一個範例引發 NoMethodError

# spec/spec_helper.rb
RSpec.configure do |config|
  config.expose_dsl_globally = false
end

感謝 Jon Rowe 實作此功能。

更多資訊

使用 alias_example_group_to 定義範例群組別名

在 RSpec 2.x 中,我們提供了一個 API,允許您定義具有附加中繼資料的 example 別名。例如,這在內部用於將 fit 定義為 it 的別名,其中包含 :focus => true 中繼資料

# spec/spec_helper.rb
RSpec.configure do |config|
  config.alias_example_to :fit, :focus => true
end

在 RSpec 3 中,我們已將此功能擴充到範例群組

# spec/spec_helper.rb
RSpec.configure do |config|
  config.alias_example_group_to :describe_model, :type => :model
end

您可以在使用 rspec-rails 的專案中使用此範例,並使用 describe_model User 而不是 describe User, :type => :model

感謝 Michi Huber 實作此功能。

更多資訊

新的範例群組別名:xdescribexcontextfdescribefcontext

除了包含一個用於定義範例群組別名的 API 之外,我們還包含了一些額外的內建別名(在 describecontext 之上)

更多資訊

pending 語意的變更(並引入 skip

現在會執行擱置範例,以檢查它們是否實際通過。如果擱置區塊失敗,則會像之前一樣標示為擱置。但是,如果它成功,則會導致失敗。這有助於確保擱置的範例有效,並在實作其描述的行為時立即處理它們。

為了支援舊的「永不執行」行為,已新增 skip 方法和中繼資料。永遠不會執行以下任何範例

describe Post do
  skip 'not implemented yet' do
  end

  it 'does something', :skip => true do
  end

  it 'does something', :skip => 'reason explanation' do
  end

  it 'does something else' do
    skip
  end

  it 'does something else' do
    skip 'reason explanation'
  end
end

有了此變更,將區塊傳遞至範例中的 pending 不再有意義,因此已移除該行為。

感謝 Xavier Shay 實作此功能。

更多資訊

單行程式碼的新 API:is_expected

RSpec 已有多年的單行語法

describe Post do
  it { should allow_mass_assignment_of(:title) }
end

在此內容中,should 並「不是」猴子修補的 should,您可以透過將 rspec-expectations 設定為僅支援 :expect 語法來移除該語法。它沒有猴子修補 Objectshould 所帶來的負擔,並且無論您的語法設定如何,始終可用。

一些使用者對此 should 如何與 expect 語法相關以及是否可以繼續使用它表示困惑。它將繼續在 RSpec 3 中提供(再次,無論您的語法設定如何),但我們也新增了一個替代 API,它與 expect 語法更一致

describe Post do
  it { is_expected.to allow_mass_assignment_of(:title) }
end

is_expected 非常簡單地定義為 expect(subject),並且也透過 is_expected.not_to matcher 支援負面預期。

更多資訊

可以個別排序範例群組

RSpec 2.8 為 RSpec 引入了隨機排序,這對於顯示您的規格套件中無意的排序相依性非常有用。在 RSpec 3 中,它不再是全有或全無的功能。您可以透過使用適當的中繼資料標記範例群組來控制它們的排序方式

describe MyClass, :order => :defined do
  # examples in this group will always run in defined order,
  # regardless of any other ordering configuration.
end

describe MyClass, :order => :random do
  # examples in this group will always run in random order,
  # regardless of any other ordering configuration.
end

這對於從已定義的排序移轉至隨機排序特別有用,因為它允許您在為特定群組選擇加入該功能時逐一處理排序相依性,而不是必須一次解決所有問題。

作為此的一部分,我們還將 --order default 重新命名為 --order defined,因為我們意識到「default」是一個過於重載的詞彙。

感謝 Andy LindemanSam Phippen 協助實作此功能。

更多資訊

新的排序策略 API

在 RSpec 3 中,我們徹底修改了排序策略 API。過去的 三個 不同的 方法 現在整合為單一方法:register_ordering。您可以使用它來定義具名的排序策略

# spec/spec_helper.rb
RSpec.configure do |config|
  config.register_ordering(:description_length) do |list|
    list.sort_by { |item| item.description.length }
  end
end
describe MyClass, :order => :description_length do
  # ...
end

或者,您可以使用它來定義全域排序

# spec/spec_helper.rb
RSpec.configure do |config|
  config.register_ordering(:global) do |list|
    # sort them alphabetically
    list.sort_by { |item| item.description }
  end
end

:global 排序用於排序最上層的範例群組,以及所有沒有 :order 元資料的範例群組。

更多資訊

rspec --init 的改進

rspec 命令長期以來都提供 --init 選項,以建立專案的骨架。在 RSpec 3 中,它產生的檔案已大幅改進,以提供更好的開箱即用體驗,並提供包含更多建議設定的 spec/spec_helper.rb 檔案。

請注意,未來的預設設定中不會包含的建議設定,會在產生的檔案中註解掉,因此開啟檔案並接受您想要的建議是個好主意。

更多資訊

新的 --dry-run CLI 選項

此選項將印出您的測試套件的格式器輸出,而不會執行任何範例或 hook。它特別適用於檢閱您的套件文件輸出,而無需等待規格執行或擔心它們的通過/失敗狀態。

感謝 Thomas Stratmann 貢獻 這個功能!

更多資訊

格式器 API 的變更

已新增一個全新的格式器 API,更具彈性。

新的格式器看起來像這樣

class CustomFormatter
  RSpec::Core::Formatters.register self, :example_started

  def initialize(output)
    @output = output
  end

  def example_started(notification)
    @output << "example: " << notification.example.description
  end
end

為了繼續支援舊的 2.x 格式器 API,我們提供了 rspec-legacy_formatters gem

感謝 Jon Rowe 負責處理這件事。

更多資訊

斷言設定變更

雖然大多數使用者使用 rspec-expectations,但使用其他工具也很簡單,RSpec 2.x 透過設定選項,讓最常見的替代方案更容易取得

# spec/spec_helper.rb
RSpec.configure do |config|
  config.expect_with :stdlib
  # or, to use both:
  config.expect_with :stdlib, :rspec
end

然而,對於 :stdlib 一直存在困惑。在 Ruby 1.8 上,標準函式庫斷言模組是 Test::Unit::Assertions。在 1.9+ 上,它是 Minitest::Assertions 的薄包裝器(而且您通常最好直接使用它)。同時,還有一個定義 Test::Unit::Assertions 的 test-unit gem(它*不是* minitest 的包裝器)和一個 minitest gem。

對於 RSpec 3,我們已移除 expect_with :stdlib,改為明確的 :test_unit:minitest 選項

# spec/spec_helper.rb
RSpec.configure do |config|
  # for test-unit:
  config.expect_with :test_unit

  # for minitest:
  config.expect_with :minitest
end

感謝 Aaron Kromer 實作了這個功能

更多資訊

定義衍生元資料

RSpec 的元資料系統非常靈活,允許您以多種方式切割和剖析您的測試套件。有一個新的設定 API 允許您定義衍生的元資料。例如,自動將 spec/acceptance/js 中的所有範例群組標記為 :js => true

# spec/spec_helper.rb
RSpec.configure do |config|
  config.define_derived_metadata(:file_path => %r{/spec/acceptance/js/}) do |metadata|
    metadata[:js] = true
  end
end

更多資訊

移除

一些不再是 RSpec 核心的功能,已被完全移除或提取到外部 gem 中

rspec-expectations

使用 should 語法而不明確啟用它已過時

在 RSpec 2.11 中,我們開始透過引入新的基於 expect 的語法,朝著消除 RSpec 的 monkey patching 方向邁進。在 RSpec 3 中,我們保留了 should 語法,並且預設可用,但如果您使用它而不明確啟用它,將會收到棄用警告。這將為它在 RSpec 4 中預設停用(或可能提取到單獨的 gem 中)鋪路,同時最大限度地減少新手透過舊的教學課程接觸 RSpec 時的困惑。

我們認為 expect 語法現在是 RSpec 的「主要」語法,但如果您偏好較舊的基於 should 的語法,可以繼續使用它:我們沒有計畫要永遠殺死它。

感謝 Sam Phippen 實作了這個功能。

更多資訊

複合匹配器表達式

在 RSpec 3 中,您可以使用 andor 將多個匹配器串聯在一起

# these two expectations...
expect(alphabet).to start_with("a")
expect(alphabet).to end_with("z")

# ...can be combined into one expression:
expect(alphabet).to start_with("a").and end_with("z")

# You can also use `or`:
expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")

它們被別名為 &| 運算符

expect(alphabet).to start_with("a") & end_with("z")
expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")

感謝 Eloy Espinaco 建議並實作了這個功能,並感謝 Adam Farhi 使用 &| 運算符擴展了它

更多資訊

可組合的匹配器

RSpec 3 允許您透過將匹配器作為參數傳遞給其他匹配器來表達詳細的意圖

s = "food"
expect { s = "barn" }.to change { s }.
  from( a_string_matching(/foo/) ).
  to( a_string_matching(/bar/) )

expect { |probe|
  "food".tap(&probe)
}.to yield_with_args( a_string_starting_with("f") )

為了提高程式碼表達式和失敗訊息的可讀性,大多數匹配器都有別名,在這些類型的表達式中作為參數傳遞時可以正確讀取。

更多資訊

match 匹配器可以用於資料結構

在 RSpec 3 之前,match 匹配器使用 #match 方法來執行字串/regex 匹配

expect("food").to match("foo")
expect("food").to match(/foo/)

在 RSpec 3 中,它還支援匹配任意巢狀的陣列/雜湊資料結構。預期的值可以使用任何巢狀層級的匹配器來表示

hash = {
  :a => {
    :b => ["foo", 5],
    :c => { :d => 2.05 }
  }
}

expect(hash).to match(
  :a => {
    :b => a_collection_containing_exactly(
      an_instance_of(Fixnum),
      a_string_starting_with("f")
    ),
    :c => { :d => (a_value < 3) }
  }
)

更多資訊

新的 all 匹配器

此匹配器可讓您指定集合中所有項目都為 true。將匹配器作為參數傳遞

expect([1, 3, 5]).to all( be_odd )

感謝 Adam Farhi 貢獻了這個功能!

更多資訊

新的 output 匹配器

此匹配器可用於指定程式碼區塊寫入 stdout 或 stderr

expect { print "foo" }.to output("foo").to_stdout
expect { print "foo" }.to output(/fo/).to_stdout
expect { warn  "bar" }.to output(/bar/).to_stderr

感謝 Matthias Günther 提出建議(並讓事情開始運作),並感謝 Luca Pette 將這個功能完成

更多資訊

新的 be_between 匹配器

RSpec 2 使用動態述詞支援為實作 between? 的物件提供了一個 be_between 匹配器。在 RSpec 3 中,我們獲得了一個更好的第一級 be_between 匹配器,它在幾個方面都更好

# like `Comparable#between?`, it is inclusive by default
expect(10).to be_between(5, 10)

# ...but you can make it exclusive:
expect(10).not_to be_between(5, 10).exclusive

# ...or explicitly label it inclusive:
expect(10).to be_between(5, 10).inclusive

感謝 Erik Michaels-Ober 貢獻了這個功能,並感謝 Pedro Gimenez 改進了它!

更多資訊

布林匹配器已重新命名

RSpec 2 有一對匹配器(be_truebe_false)反映了 Ruby 的條件語意:be_true 對於 nilfalse 以外的任何值都會通過,而 be_false 對於 nilfalse 都會通過。

在 RSpec 3 中,我們將這些重新命名為 be_truthybe_falsey(或 be_falsy,如果您喜歡這個拼寫),使其語意更明確,並減少與 be true/be false 的混淆(它們讀起來與 be_true/be_false 相同,但僅在給出確切的 true/false 值時才通過)。

感謝 Sam Phippen 實作了這個功能。

更多資訊

match_array 匹配器現在可作為 contain_exactly 使用

RSpec 長期以來都有一種匹配器,可讓您匹配兩個陣列的內容,同時忽略任何排序差異。最初,這可以使用舊的 should 語法的 =~ 運算符來使用

[2, 1, 3].should =~ [1, 2, 3]

後來,當我們新增 expect 語法時,我們決定不將運算符匹配器帶到新的語法中,並將匹配器命名為 match_array

expect([2, 1, 3]).to match_array([1, 2, 3])

match_array 是我們當時能想到的最好名稱,但我們對此並不是很滿意:「match」是一個不精確的術語,而且匹配器旨在處理陣列以外的其他種類的集合。我們在 RSpec 3 中想出了更好的名稱

expect([2, 1, 3]).to contain_exactly(1, 2, 3)

請注意,match_array *並未* 被棄用。這兩個方法的行為相同,只是 contain_exactly 會將項目個別展開傳遞,而 match_array 則會接受單一陣列參數。

更多資訊

集合基數匹配器提取到 rspec-collection_matchers gem 中

集合基數匹配器 — have(x).itemshave_at_least(y).itemshave_at_most(z).items — 是 RSpec 中比較「神奇」且令人困惑的部分之一。它們已提取rspec-collection-matchers gem 中,Hugo Baraúna 很樂意自願維護它。

一般替代方案是對集合的大小設定期望

expect(list).to have(3).items
# ...can be written as:
expect(list.size).to eq(3)

expect(list).to have_at_least(3).items
# ...can be written as:
expect(list.size).to be >= 3

expect(list).to have_at_most(3).items
# ...can be written as:
expect(list.size).to be <= 3

改進與 Minitest 的整合

在 RSpec 2.x 中,rspec-expectations 會自動將其自身包含MiniTest::Unit::TestCaseTest::Unit::TestCase 中,如此一來您只需載入它,便能在 Minitest 或 Test::Unit 中使用 rspec-expectations。

在 RSpec 3 中,我們以幾種方式更新了這個整合。

更多資訊

Matcher 協定的變更

如上所述,在 RSpec 3 中,我們不再認為 should 是 rspec-expectations 的主要語法。我們已更新 matcher 協定以反映這一點。

此外,我們還新增了 supports_block_expectations? 作為 matcher 協定中新的可選部分。這用於在使用者錯誤地在區塊期望表達式中使用值 matcher 時,給予使用者清楚的錯誤訊息。例如,在進行此變更之前,當使用像 be_nil 這樣的 matcher 時,將區塊傳遞給 expect 可能會導致誤判。

expect { foo.bar }.not_to be_nil

# ...is equivalent to:
block = lambda { foo.bar }
expect(block).not_to be_nil

# ...but the block is not nil (even though `foo.bar` might return nil),
# so the expectation will pass even though the user probably meant:
expect(foo.bar).not_to be_nil

請注意,supports_block_expectations? 是協定中可選的一部分。對於不打算在區塊期望表達式中使用的 matcher,您不需要定義它。

更多資訊

rspec-mocks

在未明確啟用它的情況下使用 monkey-patched 語法已被棄用。

與 rspec-expectations 一樣,我們也一直在將 rspec-mocks 朝向零 monkey patching 語法的方向發展。這在 2.14 版中最初引入。在 RSpec 3 中,如果您在未明確啟用的情況下使用原始語法(例如 obj.stubobj.should_receive 等),您將收到棄用警告(就像 rspec-expectations 的新語法一樣)。

感謝 Sam Phippen 實作 此功能。

新語法的 receive_messagesreceive_message_chain

最初的 monkey patching 語法有一些在 2.14 版發布的新語法中缺乏的功能。我們在 RSpec 3 中透過幾個新的 API 解決了這個問題:receive_messagesreceive_message_chain

# old syntax:
object.stub(:foo => 1, :bar => 2)
# new syntax:
allow(object).to receive_messages(:foo => 1, :bar => 2)

# old syntax:
object.stub_chain(:foo, :bar, :bazz).and_return(3)
# new syntax:
allow(object).to receive_message_chain(:foo, :bar, :bazz).and_return(3)

這些新 API 的一個好處是它們也可以與 expect 一起使用,而在舊語法中,沒有與 stub(hash)stub_chain 對等的訊息期望。

感謝 Jon RoweSam Phippen 實作此功能。

更多資訊

已移除 mockstub 作為 double 的別名

過去,rspec-mocks 提供了 3 個方法來建立測試替身:mockstubdouble。在 RSpec 3 中,我們已移除 mockstub,只保留 double,並建立了更多使用 double 命名法的功能(例如驗證替身,請參閱下文)。

當然,雖然 RSpec 3 不再提供 mockstub 作為 double 的別名,但如果您想繼續使用它們,您可以輕鬆地自行定義這些別名。

# spec/spec_helper.rb
module DoubleAliases
  def mock(*args, &block)
    double(*args, &block)
  end
  alias stub mock
end

RSpec.configure do |config|
  config.include DoubleAliases
end

感謝 Sam Phippen 實作 此功能。

更多資訊

驗證替身

新增了一種新的替身類型,它確保您只 stub 或 mock 實際存在的方法,並且傳遞的參數符合宣告的方法簽名。如果未滿足這些條件,則 instance_doubleclass_doubleobject_double 替身都會引發例外。如果尚未載入類別(通常是在隔離執行單元測試時),則不會引發任何例外。

這是一個細微的行為,但非常強大,因為它允許隔離單元測試的速度,並具有接近整合測試(或類型系統)的信心。很少有理由不使用這些新的、更強大的替身類型。

感謝 Xavier Shay 提供這個功能的想法和實作。

更多資訊

部分替身驗證設定選項

也可以在部分替身上全域啟用驗證替身行為。(部分替身是指當您 mock 或 stub 現有物件時:expect(MyClass).to receive(:some_message)。)

# spec/spec_helper.rb
RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

我們建議您為所有新程式碼啟用此選項。

範圍變更

rspec-mocks 的操作是根據每個測試的生命週期設計的。這已在 RSpec 2 中記錄,但並非總是在執行時明確強制執行,當使用者嘗試在每個測試生命週期之外使用 rspec-mocks 的功能時,我們有時會收到錯誤報告。

在 RSpec 3 中,我們已經加強了這一點,並且這個生命週期在執行時會明確強制執行。

我們還提供了一個新的 API,讓您可以在任意位置(例如 before(:context) hook)建立臨時範圍。

describe MyWebCrawler do
  before(:context) do
    RSpec::Mocks.with_temporary_scope do
      allow(MyWebCrawler).to receive(:crawl_depth_limit).and_return(5)
      @crawl_results = MyWebCrawler.perform_crawl_on("http://some-host.com/")
    end # verification and resets happen when the block completes
  end

  # ...
end

感謝 Sam Phippen 協助 實作 這些變更,以及 Sebastian Skałacki 建議新的 with_temporary_scope 功能。

更多資訊

any_instance 實作區塊會產生接收器

當為方法 stub 提供實作區塊時,根據物件的狀態進行一些計算可能很有用。不幸的是,在 RSpec 2 中使用 any_instance 時,沒有簡單的方法可以做到這一點。在 RSpec 3 中,接收器會作為第一個參數產生到 any_instance 實作區塊,使其變得容易。

allow_any_instance_of(Employee).to receive(:salary) do |employee, currency|
  usd_amount = 50_000 + (10_000 * employee.years_worked)
  currency.from_usd(usd_amount)
end

employee = Employee.find(23)
salary = employee.salary(Currency.find(:CAD))

感謝 Sam Phippen 實作 此功能。

更多資訊

rspec-rails

預設情況下停用檔案類型推斷

rspec-rails 會根據規格在檔案系統上的位置自動新增元數據。這會讓新使用者感到困惑,並且對某些資深使用者來說並不理想。

在 RSpec 3 中,必須明確啟用此行為。

# spec/spec_helper.rb
RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

由於這種假設的行為在教學中非常普遍,因此預設產生的設定仍然會啟用此功能。

若要明確標記規格而不使用自動推斷,請設定 type 元數據。

RSpec.describe ThingsController, type: :controller do
  # Equivalent to being in spec/controllers
end

每個不同的規格類型中都有記錄不同的可用類型,例如控制器規格的文件

更多資訊

已提取 activemodel mocks 支援

mock_modelstub_model 已被提取到 rspec-activemodel-mocks gem 中。

感謝 Thomas Holmes 進行提取並提供維護新 gem。

已移除 webrat 支援

Webrat 支援已移除。請改用 capybara。

匿名控制器改進

rspec-rails 長期以來允許您建立用於測試的匿名控制器。在 RSpec 3 中,它們獲得了一些改進。

更多資訊

最後的話

一如既往,每個子專案都有完整的變更日誌。

RSpec 3 是 RSpec 近 4 年來的首次主要發布。它代表了大量貢獻者的大量工作。

無論您如何使用 RSpec,我們都希望您喜歡這些新的變更。

感謝 Xavier Shay 協助撰寫這篇部落格文章,以及 Jon Rowe、Sam Phippen 和 Aaron Kromer 校對。