當在撰寫測試時,肯定會遇到是在處理外部服務(api)的事情。
正因為我也剛好使用了 coverage 這個 gem,來看看測試覆蓋率。
不管怎麼做,就是沒有頭緒,不知道怎麼在測試裡執行裡面的程式。
我們也必須確保這段程式沒有誤。

直到我用了 webmock 這個 gem,也就是今天的主角。
webmock 是用於在Ruby中對HTTP請求進行 stub 設置與期望的結果。它允許我們對HTTP請求進行stub,並設置和驗證對任何HTTP請求的期望。也能使用 Net::HTTP 當然也可以使用 HTTParty

當我們在 Rspec 使用 webmock
需要設定與讓我們通過禁用我們的測試套件來確保它們不能發出外部請求

1
2
3
4
# spec_helper.rb

require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true)

我設定了一個 UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class UserService
def initialize(email)
@email = email
end

def user_profile
HTTParty.get('https://demo.com/profile', query: { user: @email }, headers: { 'Content-Type': 'application/json' })
# return
# { status: 200, parsed_resp: { name: 'willy', age: 18 } }
end

def add_new_user
HTTParty.post('https://demo.com/new_user', body: { user: @user }, headers: { 'Content-Type': 'application/json' })
# return
# { status: 'ok' }
end
end

這是用來模擬可以用 email,來獲取使用者資料或是新增使用者的 service

接下來就可以開始撰寫我們的測試了

首先我們針對 #user_profile 這個 method 開始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require 'rails_helper'

RSpec.describe UserService do
describe '#user_profile' do
it 'Correct URI' do
# 這裡我們需要 stub 我們要處理的 api uri
# 我們可以設定 headers 或是 body or query params
stub_request(:get, 'https://demo.com/profile').with(
headers: { 'Content-Type': 'application/json' },
query: hash_including({ user: 'willy@gmail.com' })
).to_return(body: { status: 200, name: 'willy', age: 18 }.to_json)
# to_return 就是模擬 api 回傳的結果
resp = UserService.new('willy@gmail.com').user_profile
result = JSON.parse(resp)

expect(result['status']).to eq(200)
expect(result['name']).to eq('willy')
expect(result['age']).not_to eq(30)
end
end
end

我們把 uri 換成別的看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it 'Wrong URI' do
# 這裡我們需要 stub 換成別的網址看看
# 我們可以設定 headers 或是 body or query params
stub_request(:get, 'https://test.com/profile').with(
headers: { 'Content-Type': 'application/json' },
query: hash_including({ user: 'willy@gmail.com' })
).to_return(body: { status: 200, name: 'willy', age: 18 }.to_json)
resp = UserService.new('willy@gmail.com').user_profile
result = JSON.parse(resp)

expect(result['status']).to eq(200)
expect(result['name']).to eq('willy')
expect(result['age']).not_to eq(30)
end

沒意外的話,在 run rspec 時,會得到下面的回應

1
WebMock::NetConnectNotAllowedError

我們也可以stub_request(:post)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe '#add_new_user' do
it 'create user success' do
stub_request(:post, 'https://demo.com/new_user').with(
headers: { 'Content-Type' => 'application/json' }
).to_return(body: { status: 200, message: 'ok' }.to_json)
resp = UserService.new('willy@gmail.com').add_new_user
result = JSON.parse(resp)

expect(result['status']).to eq(200)
expect(result['message']).to eq('ok')
end
# it 'user already exists' do
# end
end
end

結論:

  1. stub_requesturi 必須跟原本的 get or posturi 一樣
    1
    2
    3
    例如我在 `#user_profile`
    HTTParty.get('https://demo.com/profile')
    那我 stub_request(:get, 'https://demo.com/profile') 就必須跟上面一樣,否則就會出現上面的 error
  2. 我們要確保我們的測試套件不會影響任何第三方服務。我們的測試應該獨立運行。
  3. 加速我們針對測試的撰寫

-W