Rspec: Định nghĩa matcher riêng

Các matcher được xây dựng sẵn trong Rspec rất tốt nhưng không đầy đủ. May mắn là chúng ta có thể tạo ra được những matcher riêng, và đương nhiên chúng ta có thể sử dụng chúng trên toàn bộ project. Để chứng minh một cách rõ hơn vì sao chúng ta cần phải định nghĩa một matcher riêng, chúng ta sẽ sử dụng lớp Persion đại diện cho người sử dụng ứng dụng. Chúng ta sẽ bắt đầu theo hướng TDD(Test Driven Development). Yêu cầu như sau:

  • Có field role với giá trị default là user.
  • Có fiend verified với giá trị default là false.
  • Có thể nâng cấp lên admin và tự động verify.

Chúng ta có thể tạo ra một đối tượng trong Ruby đơn giản và focus vào việc nâng cấp role lên admin. Đặt tên của nó là upgrade_to_admin:

require 'spec_helper'

describe Person do
  it 'sets person role to user by the default' do
    person = Person.new
      
    expect(person.role).to eq('user')
  end
    
  it 'makes the person not verified by the default' do
    person = Person.new
      
    expect(person.verified).to eq(false)
  end
  
  describe '#upgrade_to_admin' do
    it 'upgrades to admin' do
      person = Person.new
      person.upgrade_to_admin

      expect(person.role).to eq('admin')
      expect(person.verified).to eq(true)
    end
  end
end

Chúng ta hãy tạo ra lớp Persion thoả mãn yêu cầu ở trên:

class Person
  attr_reader :role, :verified

  def initialize
    @role = 'user'
    @verified = false
  end

  def upgrade_to_admin
    @role = 'admin'
    @verified = true
  end
end

Custom matcher

Hãy tập trung vào việc liệu đối tượng đã có role chưa. Chúng ta sẽ xây dựng custom matcher cho đoạn test sau:

expect(person.role).to eq('user')

Vì chúng ta sử sử dụng TDD để tiếp cận nên đầu tiên chúng ta sẽ viết như sau:

expect(person).to be_user

Nếu làm như thế này chúng ta sẽ thấy có quá nhiều cú pháp xuất hiện. Đây là một ví dụ không tốt về việc tạo ra custom matcher nhưng là một ví dụ đơn giản phục vụ cho mục đích của bài viết, đó là giới thiệu cách tạo ra custom matcher và vì vậy hãy đừng để ý đến vấn đề này. Tạo tập tin mới trong thư mục spec/support/ và đặt tên là matchers.rb hoặc bất cứ cái gì bạn muốn. Chúng ta sẽ sử dụng một chút metaprogramming ở đây bằng cách sử dụng RSpec DSL:

RSpec::Matchers.define :be_user do |expected|
  match do |actual|
    expect(actual.role).to eq('user')
  end
end

Chúng ta có 2 biến số ở đây:

  • expected là giá trị được truyền cho matcher. Vì chúng ta không truyền tham số nào cho be_user nên nó chứa giá trị nil.
  • actual là tham số truyền vào expect

Bước cuối cùng là require matchers.rb. Để thực hiện việc này, hãy chỉnh sửa spec_helper.rb hoặc rails_helper.rb và sử dụng require:

require 'support/matchers'

Bây giờ hãy chạy lại test của bạn.

Update các mô tả mặc định

Chúng ta có thể đơn giản hoá rspec chỉ còn 1 dòng như sau:

it { expect(Person.new).to be_user }

Bây giờ chúng ta chạy rspec với flag --format documentation. Sẽ nhận được output đơn giản là:

Person
  should be user

Chúng ta có thể control nó. Chúng ta cần update lại custom matcher, và sử dụng description block:

RSpec::Matchers.define :be_user do |expected|
  match do |actual|
    expect(actual.role).to eq('user')
  end
  
  description do
    "have user role and be unverified"
  end
end

Chạy lại rspec và nhận được kết quả khác với lúc trước:

Person
  should have user role and be unverified

Điều này sẽ làm cho mô tả rspec có ý nghĩa hơn.

Kết luận

Trên đây chỉ là ví dụ đơn giản giúp bạn biết đc cách thức hoạt động và có thể tạo ra custom matcher cho riêng mình. Tuy nhiên việc sử dụng nó cũng cần phải đúng lúc đúng chỗ. Ngoài ra chúng ta có thể tìm hiểu thêm về kỹ thuật TDD. Nó sẽ giúp ích rấ nhiều về lối tư duy cũng như việc code dễ dàng hơn. Tài liệu về TDD: RSpec & Test Driven Development Refereces: http://pdabrowski.com/blog/ruby-on-rails/testing/rspec-custom-matchers/

All Rights Reserved