copy from: How to Stub External Services in Tests
如果我们在测试中会访问到外部的服务,往往会有下面的问题:
- 如果有网络连接问题就会让测试失败
- 拖慢测试
- 一般外部的第三方 API 还有访问数限制, 如 twitter
- 可能你会和队友合作,一起开发,他们的API 还没有开发完成呢,只有一个式样的规范
- 我们在测试,而第三方的 API 不提供测试环境啊,你怎么测试.
当我们的程序和外部交互的时候,我们需要确保我们的测试 case 没有访问到第三方的服务.我们的测试应该是隔离的.
断了网络连接
我们要使用Webmock
,可以用来stub外部 http request
的 gem
, 在这个例子里,我们需要通过 Github API
来搜索 FactoryGirl
的贡献者!
首先,我们要在spec_helper.rb
里禁用外部连接. 这样的:
# spec/spec_helper.rb
require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true)
现在来看看是否会有异常:
# spec/features/external_request_spec.rb
require 'spec_helper'
feature 'External request' do
it 'queries FactoryGirl contributors on GitHub' do
uri = URI('https://api.github.com/repos/thoughtbot/factory_girl/contributors')
response = Net::HTTP.get(uri)
expect(response).to be_an_instance_of(String)
end
end
这个的情况下,我们应该看见这些预期的异常:
$ rspec spec/features/external_request_spec.rb
F
Failures:
1) External request queries FactoryGirl contributors on GitHub
Failure/Error: response = Net::HTTP.get(uri)
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled.
Unregistered request: GET https://api.github.com/repos/thoughtbot/factory_girl/contributors
with headers {
'Accept'=>'*/*',
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'Host'=>'api.github.com',
'User-Agent'=>'Ruby'
}
You can stub this request with the following snippet:
stub_request(:get, "https://api.github.com/repos/thoughtbot/factory_girl/contributors").
with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Host'=>'api.github.com', 'User-Agent'=>'Ruby'}).
to_return(:status => 200, :body => "", :headers => {})
============================================================
# ./spec/features/external_request_spec.rb:8:in `block (2 levels) in <top (required)>'
Finished in 0.00499 seconds
1 example, 1 failure
我们怎么处理这个情况呢,可以通过 stub 这个 api 的请求,看看:
# spec/spec_helper.rb
RSpec.configure do |config|
config.before(:each) do
stub_request(:get, /api.github.com/).
with(headers: {'Accept'=>'*/*', 'User-Agent'=>'Ruby'}).
to_return(status: 200, body: "stubbed response", headers: {})
end
end
现在再次跑这个测试看看:
$ rspec spec/features/external_request_spec.rb
.
Finished in 0.01116 seconds
1 example, 0 failures
VCR
另外一个方式就是为了防止外部的请求,可以通过录制一个API 的访问,然后当再次有 API 的请求时就把结构回放
给调用方, 这个 VCR
gem 有一个 cassettes
的概念,他能够录制测试 case访问外部的 http
请求,然后以供后需.
当你的使用 VCR 的时候你需要注意下面的方式:
- 和其他人沟通怎么共享这个 cassette
- 第一次访问这个外部请求需成功
- 很难模拟失败
接下来,我们要创建一个假版本的 Github
服务.
创建一个假的服务(用 sinatra)
当你的程序严重依赖第三方服务的时候,需要考虑通过 sinatra
来在你的程序中创建一个假的服务.它会让你的测试完全隔离起来.而且可以很容易控制测试的响应时间.
首先,我们通过 Webmock
来把所有的请求路由到这个我们新建的 Sinatra
程序里-- FakeGithub
.
# spec/spec_helper.rb
RSpec.configure do |config|
config.before(:each) do
stub_request(:any, /api.github.com/).to_rack(FakeGitHub)
end
end
我们来创建这个假的服务;
# spec/support/fake_github.rb
require 'sinatra/base'
class FakeGitHub < Sinatra::Base
get '/repos/:organization/:project/contributors' do
json_response 200, 'contributors.json'
end
private
def json_response(response_code, file_name)
content_type :json
status response_code
File.open(File.dirname(__FILE__) + '/fixtures/' + file_name, 'rb').read
end
end
下载这个例子的 json
响应.然后保存在本地文件中
# spec/support/fixtures/contributors.json
[
{
"login": "joshuaclayton",
"id": 1574,
"avatar_url": "https://2.gravatar.com/avatar/786f05409ca8d18bae8d59200156272c?d=https%3A%2F%2Fidenticons.github.com%2F0d4f4805c36dc6853edfa4c7e1638b48.png",
"gravatar_id": "786f05409ca8d18bae8d59200156272c",
"url": "https://api.github.com/users/joshuaclayton",
"html_url": "https://github.com/joshuaclayton",
"followers_url": "https://api.github.com/users/joshuaclayton/followers",
"following_url": "https://api.github.com/users/joshuaclayton/following{/other_user}",
"gists_url": "https://api.github.com/users/joshuaclayton/gists{/gist_id}",
"starred_url": "https://api.github.com/users/joshuaclayton/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/joshuaclayton/subscriptions",
"organizations_url": "https://api.github.com/users/joshuaclayton/orgs",
"repos_url": "https://api.github.com/users/joshuaclayton/repos",
"events_url": "https://api.github.com/users/joshuaclayton/events{/privacy}",
"received_events_url": "https://api.github.com/users/joshuaclayton/received_events",
"type": "User",
"site_admin": false,
"contributions": 377
}
]
更新测试,验证是不是返回了预期 stub 的响应.
require 'spec_helper'
feature 'External request' do
it 'queries FactoryGirl contributors on GitHub' do
uri = URI('https://api.github.com/repos/thoughtbot/factory_girl/contributors')
# 看好,这里是 JSON.load 坑爹!!!
response = JSON.load(Net::HTTP.get(uri))
expect(response.first['login']).to eq 'joshuaclayton'
end
end
跑这个 specs
$ rspec spec/features/external_request_spec.rb
.
Finished in 0.04713 seconds
1 example, 0 failures
✌️成功了!
这个可以让我们完全推理外部 api 的依赖.
但是在创建一个假的服务的时候,需要注意下面的几件事情:
- 如果有了这个额额外的服务,就需要额外的维护哦
- 可能你的这个服务返回的内容过期了哦!
-EOF-