my_api_client 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 796e9513f74e5c4fa6366ac0a67b5eea77a9e0c32f39325acc8a8702ef9852f1
4
- data.tar.gz: 274bd8ec8be1b10cc1b39ae683579dfb0e2f72dac7a1eab2a8cc018baf3c5335
3
+ metadata.gz: 6cd0cadf555eff5fcc7b6ce43f2af6437c89243fa371b8c17eb6db893948c256
4
+ data.tar.gz: 9c69a2495c109a11c601bbe770f4f063fa4783f72d567d5b3b4f1a3ded514c99
5
5
  SHA512:
6
- metadata.gz: b2fbd02137213962a81a896d930a046360134b0c1cf600f0cf2f2f6b0e5d44d12b94a560c903d80e215dda9374fe4cca9bd558bafe887974de8ba5a1ceaa19ca
7
- data.tar.gz: c1fe1f75598e3f597e3c52f32b6b32aee80ec4d9cfd061dcb2a1765955020b5b35cd161112a6d57121b72917773558f065b097bac4324e6aa5c1df3f18f3390d
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-09-30 23:40:39 +0000 using RuboCop version 0.75.0.
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.9.2)
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.4)
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.13.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.0)
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
- ## Testing
304
+ ## RSpec
305
305
 
306
- ### RSpec
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
- 例えば以下のような `ApiClient` を定義しているとします。
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class <%= "#{class_name}ApiClient" %> < ::ApplicationApiClient
4
- endpoint '{ endpoint }'
4
+ endpoint 'https://example.com'
5
5
 
6
6
  # error_handling json: { '$.errors.code': 10 } do |params, logger|
7
7
  # # Behavior when detected an error.
@@ -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
@@ -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
@@ -107,7 +107,7 @@ module MyApiClient
107
107
  end
108
108
 
109
109
  def agent
110
- instance_double('Sawyer::Agent').tap do |agent|
110
+ instance_double(Sawyer::Agent).tap do |agent|
111
111
  allow(agent).to receive(:parse_links) do |data|
112
112
  data ||= {}
113
113
  links = data.delete(:_links)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MyApiClient
4
- VERSION = '0.9.2'
4
+ VERSION = '0.10.0'
5
5
  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.9.2
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-08 00:00:00.000000000 Z
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