LoginSignup
0
0

More than 5 years have passed since last update.

测试中怎么 stub 外部服务

Last updated at Posted at 2015-02-07

copy from: How to Stub External Services in Tests

如果我们在测试中会访问到外部的服务,往往会有下面的问题:
1. 如果有网络连接问题就会让测试失败
2. 拖慢测试
3. 一般外部的第三方 API 还有访问数限制, 如 twitter
4. 可能你会和队友合作,一起开发,他们的API 还没有开发完成呢,只有一个式样的规范
5. 我们在测试,而第三方的 API 不提供测试环境啊,你怎么测试.

当我们的程序和外部交互的时候,我们需要确保我们的测试 case 没有访问到第三方的服务.我们的测试应该是隔离的.

断了网络连接

我们要使用Webmock,可以用来stub外部http requestgem, 在这个例子里,我们需要通过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 的时候你需要注意下面的方式:

  1. 和其他人沟通怎么共享这个 cassette
  2. 第一次访问这个外部请求需成功
  3. 很难模拟失败

接下来,我们要创建一个假版本的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 的依赖.
但是在创建一个假的服务的时候,需要注意下面的几件事情:
1. 如果有了这个额额外的服务,就需要额外的维护哦
2. 可能你的这个服务返回的内容过期了哦!

-EOF-

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0