JT's Blog

Do the right things and do the things right.

RSpec Double / Stubs / Mocks

| Comments

原本放在 RSpec,但是越寫越多,就把它獨立成一篇

Double

RSpec 裡面叫做 double(替身),目的是用來建立替身(假物件)

1
2
3
4
5
6
7
# 建立一個假的 User object 並設定是 admin
describe "double demo" do
  it "user is an admin" do
    user = double("User", name: 'james', is_admin: true)
    expect(user.is_admin).to be_truthy
  end
end

Partial Test Doubles

設定 class method return value

1
2
3
4
5
6
7
8
describe "partial test doubles" do
  it "partial test doubles" do

    allow(User).to receive(:find).and_return("100")

    expect(User.find).to eq("100")
  end
end

Null object doubles

使用 as_null_object,確保執行未知的 method 不會出錯誤並回傳自己

1
2
3
4
5
6
7
8
describe "null object doubles" do
  it "null object doubles" do

    dbl = double("Some Collaborator").as_null_object

    expect(dbl.foo.bar).to be dbl
  end
end

Spies

TBD

Scope

TBD

Stubs

Stub 「不管邏輯、過程,只要建立假 method 並回傳資料或狀態」

Stub 手法用在要驗證 「回傳值」 或驗證 「狀態改變時」

rspec 3 之後改用 #allow 取代 #stub

stub (source: test-doubles—fakes-mocks-and-stubs)

Stubs 不管 #list_student_names 中間執行哪些動作,只管最後回傳結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
describe ClassRoom do
  it 'the list_student_names method should work correctly' do
    student1 = double('student')
    student2 = double('student')

    # 使用 stub 回傳假資料 (Rails 3 之前)
    #student1.stub(:name).and_return('John Smith')
    #student2.stub(:name).and_return('Jill Smith')

    # 使用 allow 取代 stub 回傳假資料 (Rails 3 之後)
    allow(student1).to receive(:name).and_return('John Smith')
    allow(student2).to receive(:name).and_return('Jill Smith')

    class_room = ClassRoom.new [student1,student2]
    expect(class_room.students_count).to eq(2)
    expect(class_room.list_student_names).to eq('John Smith,Jill Smith')
  end
end

Mocks

Mock 「模擬一物件有相同的介面,測試一個 「會收到指定訊息」 的期望,去驗證互動是否真的有發生」

Mock 的手法用在互動並取得預期結果

使用double 建立替身,產生互動過程,提供預期結果

mock (source: test-doubles—fakes-mocks-and-stubs)

範例:

實作 class ClassRoom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ClassRoom
  def initialize(students)
    @students = students
  end

  def list_student_names
    update_student_count_of_classroom
    send_mail_notify_teacher
    @students.map(&:name).join(',')
  end

  def students_count
    @students.count
  end
end

使用 double 建立假物件

1
2
3
4
5
6
7
8
9
describe ClassRoom do
  it 'the list_student_names method should work correctly' do
    student1 = double('student')
    student2 = double('student')

    class_room = ClassRoom.new [student1,student2]
    expect(class_room.students_count).to eq(2)
  end
end

Mocks vs. Stubs

Behavioral testing vs State testing

補充:Types of test double

The term “test double” is used to refer to a range of testing techniques, with the common theme that a substitute object is provided to the subject under test, taking the place of the objects it will communicate with in actual use.

  • Dummy – a pure placeholder object with no behaviour (it doesn’t respond to any messages). This is used when a method requires an argument but doesn’t interact with it in a specific test. Fake – a replacement object with real behaviour, but taking shortcuts that are helpful for testing purposes (a good example is using an in-memory database for faster testing of database-dependent code).
  • Stub – an object providing canned responses to specified messages.
  • Mock – an object that is given a specification of the messages that it must receive (or not receive) during the test if the test is to pass.
  • Spy – an object that records all messages it receives (assuming it is allowed to respond to them), allowing the messages it should have received to be asserted at the end of a test.

Reference

Comments