my_api_client 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gem_comet.yml +2 -1
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +4 -4
- data/README.jp.md +99 -3
- data/lib/generators/rails/templates/api_client.rb.erb +1 -1
- data/lib/generators/rspec/templates/api_client_spec.rb.erb +37 -0
- data/lib/my_api_client/rspec.rb +3 -0
- data/lib/my_api_client/rspec/matcher_helper.rb +30 -0
- data/lib/my_api_client/rspec/matchers/be_handled_as_an_error.rb +57 -0
- data/lib/my_api_client/rspec/matchers/request_to.rb +52 -0
- data/lib/my_api_client/rspec/stub.rb +1 -1
- data/lib/my_api_client/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cd0cadf555eff5fcc7b6ce43f2af6437c89243fa371b8c17eb6db893948c256
|
4
|
+
data.tar.gz: 9c69a2495c109a11c601bbe770f4f063fa4783f72d567d5b3b4f1a3ded514c99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b697f923d27cde7834774fc321f39a965f375619c591cb157f7e2743cdc54be44a41d160371e8c85f386159edbf6aeff269123d904c24eba0e6929ef540ec08
|
7
|
+
data.tar.gz: 8868f50cf3f9b1358b070d5a0261bc9db268ccf90bc2715a478b0a1b82bb6d9d210fd7b022caaafa6c0ee3afa63b1a0efd3566dee4a9e91e4b3541b748cd35c8
|
data/.gem_comet.yml
CHANGED
@@ -3,9 +3,10 @@
|
|
3
3
|
# $ gem install gem_comet
|
4
4
|
# $ gem_comet release {version number, like as "1.2.3"}
|
5
5
|
|
6
|
-
version: 1
|
6
|
+
version: 1.1
|
7
7
|
|
8
8
|
release:
|
9
9
|
base_branch: master
|
10
10
|
release_branch: production
|
11
11
|
version_file_path: lib/my_api_client/version.rb
|
12
|
+
changelog_file_path: CHANGELOG.md # optional
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2019-
|
3
|
+
# on 2019-10-14 23:40:54 +0000 using RuboCop version 0.75.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## 0.10.0 (Oct 23, 2019)
|
4
|
+
|
5
|
+
### Feature
|
6
|
+
|
7
|
+
* Shoulda-matchers for my_api_client ([#124](https://github.com/ryz310/my_api_client/pull/124))
|
8
|
+
|
9
|
+
### Misc
|
10
|
+
|
11
|
+
* Modify request specifications ([#120](https://github.com/ryz310/my_api_client/pull/120))
|
12
|
+
* Re-generate .rubocop_todo.yml with RuboCop v0.75.1 ([#121](https://github.com/ryz310/my_api_client/pull/121))
|
13
|
+
* ryz310/dependabot/bundler/jsonpath-1.0.5 ([#123](https://github.com/ryz310/my_api_client/pull/123))
|
14
|
+
|
3
15
|
## 0.9.2 (Oct 8, 2019)
|
4
16
|
|
5
17
|
### Bugfix
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
my_api_client (0.
|
4
|
+
my_api_client (0.10.0)
|
5
5
|
activesupport (>= 4.2.0)
|
6
6
|
jsonpath
|
7
7
|
sawyer (>= 0.8.2)
|
@@ -33,12 +33,12 @@ GEM
|
|
33
33
|
concurrent-ruby (~> 1.0)
|
34
34
|
jaro_winkler (1.5.3)
|
35
35
|
json (2.2.0)
|
36
|
-
jsonpath (1.0.
|
36
|
+
jsonpath (1.0.5)
|
37
37
|
multi_json
|
38
38
|
to_regexp (~> 0.2.1)
|
39
39
|
method_source (0.9.2)
|
40
40
|
minitest (5.12.2)
|
41
|
-
multi_json (1.
|
41
|
+
multi_json (1.14.1)
|
42
42
|
multipart-post (2.1.1)
|
43
43
|
parallel (1.18.0)
|
44
44
|
parser (2.6.5.0)
|
@@ -67,7 +67,7 @@ GEM
|
|
67
67
|
rspec-support (3.9.0)
|
68
68
|
rspec_junit_formatter (0.4.1)
|
69
69
|
rspec-core (>= 2, < 4, != 2.12.0)
|
70
|
-
rubocop (0.75.
|
70
|
+
rubocop (0.75.1)
|
71
71
|
jaro_winkler (~> 1.5.1)
|
72
72
|
parallel (~> 1.10)
|
73
73
|
parser (>= 2.6)
|
data/README.jp.md
CHANGED
@@ -301,9 +301,9 @@ class PostUserApiClient < ExampleApiClient
|
|
301
301
|
end
|
302
302
|
```
|
303
303
|
|
304
|
-
##
|
304
|
+
## RSpec
|
305
305
|
|
306
|
-
###
|
306
|
+
### Setup
|
307
307
|
|
308
308
|
RSpec を使ったテストをサポートしています。
|
309
309
|
以下のコードを `spec/spec_helper.rb` (または `spec/rails_helper.rb`) に追記して下さい。
|
@@ -312,7 +312,103 @@ RSpec を使ったテストをサポートしています。
|
|
312
312
|
require 'my_api_client/rspec'
|
313
313
|
```
|
314
314
|
|
315
|
-
|
315
|
+
### Testing
|
316
|
+
|
317
|
+
以下のような `ApiClient` を定義しているとします。
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
class ExampleApiClient < MyApiClient::Base
|
321
|
+
endpoint 'https://example.com/v1'
|
322
|
+
|
323
|
+
error_handling status_code: 200, json: { '$.errors.code': 10 },
|
324
|
+
raise: MyApiClient::ClientError
|
325
|
+
|
326
|
+
attr_reader :access_token
|
327
|
+
|
328
|
+
def initialize(access_token:)
|
329
|
+
@access_token = access_token
|
330
|
+
end
|
331
|
+
|
332
|
+
# GET https://example.com/v1/users
|
333
|
+
def get_users(condition:)
|
334
|
+
get 'users', headers: headers, query: { search: condition }
|
335
|
+
end
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
def headers
|
340
|
+
{
|
341
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
342
|
+
'Authorization': "Bearer #{access_token}",
|
343
|
+
}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
```
|
347
|
+
|
348
|
+
通常の場合 `ApiClient` を新たに定義した際にテストすべき項目が 2 つあります。
|
349
|
+
|
350
|
+
1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
|
351
|
+
2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
|
352
|
+
|
353
|
+
`my_api_client` ではこれらをテストするための Custom Matcher を用意しています。
|
354
|
+
|
355
|
+
#### 1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
|
356
|
+
|
357
|
+
例えば上述の `#get_users` の内部では、入力引数を用いて検索クエリが組み立てられていたり、 Header に `access_token` を利用したりしています。これらの値が正しくリクエストに用いられているかどうかのテストが必要となります。
|
358
|
+
|
359
|
+
この場合 `request_to` と `with` という Custom Matcher を利用することで簡単にテストを記述することが出来ます。 `expect` にはブロック `{}` を指定する必要がある点にご注意ください。他にも `with` には `body` というキーワード引数も指定できます。
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
RSpec.describe ExampleApiClient, type: :api_client do
|
363
|
+
let(:api_client) { described_class.new(access_token: 'access token') }
|
364
|
+
let(:headers) do
|
365
|
+
{
|
366
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
367
|
+
'Authorization': 'Bearer access token',
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
describe '#get_users' do
|
372
|
+
it do
|
373
|
+
expect { api_client.get_users(condition: 'condition') }
|
374
|
+
.to request_to(:get, 'https://example.com/v1/users')
|
375
|
+
.with(headers: headers, query: { condition: 'condition' })
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
```
|
380
|
+
|
381
|
+
#### 2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
|
382
|
+
|
383
|
+
次に `error_handling` についてのテストも記述していきます。ここではレスポンスのステータスコードが `200` かつ Body に `'$.errors.code': 10` という値が含まれていた場合は `MyApiClient::ClientError` を `raise` する、というエラーハンドリングが定義されています。
|
384
|
+
|
385
|
+
ここでは `be_handled_as_an_error` と `when_receive` という Custom Matcher を利用します。ここでも `expect` にはブロック `{}` を指定する必要がある点にご注意ください。
|
386
|
+
|
387
|
+
`be_handled_as_an_error` の引数には期待する例外クラスを指定します。 `when_receive` にはリクエスト結果としてどのような値が返ってきたのかを指定します。
|
388
|
+
|
389
|
+
なお、 `error_handling` で例外を発生させないケースは現在想定していないため、これ以外の Custom Matcher は定義されていません。何かユースケースがあれば教えて下さい。
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
it do
|
393
|
+
expect { api_client.get_users(condition: 'condition') }
|
394
|
+
.to be_handled_as_an_error(MyApiClient::ClientError)
|
395
|
+
.when_receive(status_code: 200, body: { errors: { code: 10 } }.to_json)
|
396
|
+
end
|
397
|
+
```
|
398
|
+
|
399
|
+
また、以下のように正常なレスポンスが返ってきた時に誤ってエラーハンドリングされていないかをテストすることもできます。
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
it do
|
403
|
+
expect { api_client.get_users(condition: 'condition') }
|
404
|
+
.not_to be_handled_as_an_error(MyApiClient::ClientError)
|
405
|
+
.when_receive(status_code: 200, body: { users: { id: 1 } }.to_json)
|
406
|
+
end
|
407
|
+
```
|
408
|
+
|
409
|
+
### Stubbing
|
410
|
+
|
411
|
+
以下のような `ApiClient` を定義しているとします。
|
316
412
|
|
317
413
|
```ruby
|
318
414
|
class ExampleApiClient < MyApiClient::Base
|
@@ -4,9 +4,46 @@ require 'rails_helper'
|
|
4
4
|
|
5
5
|
RSpec.describe <%= "#{class_name}ApiClient" %>, <%= type_metatag(:api_client) %> do
|
6
6
|
let(:api_client) { described_class.new }
|
7
|
+
let(:headers) do
|
8
|
+
{
|
9
|
+
'Content-Type': 'application/json',
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_examples 'to handle errors' do
|
14
|
+
it do
|
15
|
+
expect { api_request }
|
16
|
+
.to be_handled_as_an_error(MyApiClient::ClientError)
|
17
|
+
.when_receive(status_code: 400)
|
18
|
+
end
|
7
19
|
|
20
|
+
it do
|
21
|
+
expect { api_request }
|
22
|
+
.to be_handled_as_an_error(MyApiClient::ServerError)
|
23
|
+
.when_receive(status_code: 500)
|
24
|
+
end
|
25
|
+
end
|
8
26
|
<% yeild_request_arguments do |action, http_method, pathname| -%>
|
27
|
+
|
9
28
|
describe '#<%= action %>' do
|
29
|
+
subject(:api_request) { api_client.<%= action %> }
|
30
|
+
|
31
|
+
it do
|
32
|
+
expect { api_request }
|
33
|
+
.to request_to(:<%= http_method %>, 'https://example.com/<%= pathname %>')
|
34
|
+
<% if http_method == 'get' -%>
|
35
|
+
.with(headers: headers, query: {})
|
36
|
+
<% else -%>
|
37
|
+
.with(headers: headers, body: {})
|
38
|
+
<% end -%>
|
39
|
+
end
|
40
|
+
|
41
|
+
it_behaves_like 'to handle errors' do
|
42
|
+
it do
|
43
|
+
expect { api_request }.not_to be_handled_as_an_error
|
44
|
+
.when_receive(status_code: 200, body: nil, headers: nil)
|
45
|
+
end
|
46
|
+
end
|
10
47
|
end
|
11
48
|
<% end -%>
|
12
49
|
end
|
data/lib/my_api_client/rspec.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'my_api_client'
|
4
|
+
require 'my_api_client/rspec/matcher_helper'
|
4
5
|
require 'my_api_client/rspec/stub'
|
6
|
+
require 'my_api_client/rspec/matchers/be_handled_as_an_error'
|
7
|
+
require 'my_api_client/rspec/matchers/request_to'
|
5
8
|
|
6
9
|
RSpec.configure do |config|
|
7
10
|
config.include MyApiClient::Stub
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MyApiClient
|
4
|
+
# Helper module for rspec custom matcher
|
5
|
+
module MatcherHelper
|
6
|
+
def disable_logging
|
7
|
+
logger = instance_double(MyApiClient::Logger, info: nil, warn: nil)
|
8
|
+
allow(MyApiClient::Logger).to receive(:new).and_return(logger)
|
9
|
+
end
|
10
|
+
|
11
|
+
def dummy_response(status: 200, headers: {}, body: nil)
|
12
|
+
instance_double(
|
13
|
+
Sawyer::Response,
|
14
|
+
timing: 0.0,
|
15
|
+
data: instance_double(Sawyer::Resource),
|
16
|
+
status: status,
|
17
|
+
headers: headers,
|
18
|
+
body: body
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def diff_as_object(actual, expected)
|
23
|
+
differ = RSpec::Support::Differ.new(
|
24
|
+
object_preparer: ->(object) { RSpec::Matchers::Composable.surface_descriptions_in(object) },
|
25
|
+
color: RSpec::Matchers.configuration.color?
|
26
|
+
)
|
27
|
+
differ.diff_as_object(actual, expected)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :be_handled_as_an_error do |expected_error_class|
|
6
|
+
include MyApiClient::MatcherHelper
|
7
|
+
|
8
|
+
match do |api_request|
|
9
|
+
init
|
10
|
+
handle_error(api_request).is_a? expected_error_class
|
11
|
+
end
|
12
|
+
|
13
|
+
match_when_negated do |api_request|
|
14
|
+
init
|
15
|
+
handle_error(api_request).nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
chain :when_receive, :expected_response
|
19
|
+
|
20
|
+
failure_message do |api_request|
|
21
|
+
actual_error = handle_error(api_request)
|
22
|
+
if actual_error.nil?
|
23
|
+
"expected to be handled as #{expected_error_class.name}, " \
|
24
|
+
'but not to be handled'
|
25
|
+
else
|
26
|
+
"expected to be handled as #{expected_error_class.name}, " \
|
27
|
+
"but it was handled as #{actual_error.class.name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
failure_message_when_negated do |api_request|
|
32
|
+
actual_error = handle_error(api_request)
|
33
|
+
'expected not to be handled as an error, ' \
|
34
|
+
"but it was handled as #{actual_error.class.name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def init
|
38
|
+
disable_logging
|
39
|
+
response = dummy_response(
|
40
|
+
status: expected_response[:status_code] || 200,
|
41
|
+
headers: expected_response[:headers] || {},
|
42
|
+
body: expected_response[:body] || nil
|
43
|
+
)
|
44
|
+
sawyer = instance_double(Sawyer::Agent, call: response)
|
45
|
+
allow(Sawyer::Agent).to receive(:new).and_return(sawyer)
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_error(api_request)
|
49
|
+
api_request.call
|
50
|
+
rescue MyApiClient::Error => e
|
51
|
+
e
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
supports_block_expectations
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/expectations'
|
4
|
+
|
5
|
+
RSpec::Matchers.define :request_to do |expected_method, expected_url|
|
6
|
+
include MyApiClient::MatcherHelper
|
7
|
+
|
8
|
+
match do |api_request|
|
9
|
+
disable_logging
|
10
|
+
@expected = {
|
11
|
+
request_line: request_line(expected_method, expected_url),
|
12
|
+
body: expected_options[:body],
|
13
|
+
headers: expected_options[:headers],
|
14
|
+
query: expected_options[:query],
|
15
|
+
}.compact
|
16
|
+
@actual = {}
|
17
|
+
sawyer = instance_double(Sawyer::Agent)
|
18
|
+
allow(Sawyer::Agent).to receive(:new) do |schema_and_hostname|
|
19
|
+
@actual_schema_and_hostname = schema_and_hostname
|
20
|
+
end.and_return(sawyer)
|
21
|
+
allow(sawyer).to receive(:call) do |method, pathname, body, options|
|
22
|
+
@actual =
|
23
|
+
{
|
24
|
+
request_line: request_line(method, @actual_schema_and_hostname + pathname),
|
25
|
+
body: body,
|
26
|
+
headers: options[:headers],
|
27
|
+
query: options[:query],
|
28
|
+
}.compact
|
29
|
+
end.and_return(dummy_response)
|
30
|
+
api_request.call
|
31
|
+
@expected == @actual
|
32
|
+
end
|
33
|
+
|
34
|
+
chain :with, :expected_options
|
35
|
+
|
36
|
+
description do
|
37
|
+
"request to \"#{request_line(expected_method, expected_url)}\""
|
38
|
+
end
|
39
|
+
|
40
|
+
failure_message do
|
41
|
+
<<~MESSAGE
|
42
|
+
expected to request to "#{@expected[:request_line]}"
|
43
|
+
Diff: #{diff_as_object(@actual, @expected)}
|
44
|
+
MESSAGE
|
45
|
+
end
|
46
|
+
|
47
|
+
def request_line(method, url)
|
48
|
+
"#{method.upcase} #{url}"
|
49
|
+
end
|
50
|
+
|
51
|
+
supports_block_expectations
|
52
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: my_api_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ryz310
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -256,6 +256,9 @@ files:
|
|
256
256
|
- lib/my_api_client/params/request.rb
|
257
257
|
- lib/my_api_client/request.rb
|
258
258
|
- lib/my_api_client/rspec.rb
|
259
|
+
- lib/my_api_client/rspec/matcher_helper.rb
|
260
|
+
- lib/my_api_client/rspec/matchers/be_handled_as_an_error.rb
|
261
|
+
- lib/my_api_client/rspec/matchers/request_to.rb
|
259
262
|
- lib/my_api_client/rspec/stub.rb
|
260
263
|
- lib/my_api_client/version.rb
|
261
264
|
- my_api_client.gemspec
|