Compare commits

...

2 Commits

Author SHA1 Message Date
Matt
69e88b4d52
Version bump to 2.4.0 2022-07-28 10:04:18 +01:00
Yutaka Kamei
cfbea91a69
Support Proc type for stubbed request body (#1436)
Previously, the Faraday testing adapter compared the request body to the
stubbed body just by calling `#==` if the stubbed body is present.
Sometimes, I want to check the equality between request and stubbed body
in more advanced ways. For example, there is a case that I want to check
only the parts of the body are actually passed to Faraday instance like
this:

```ruby
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
  stub.post('/foo', '{"name:"YK","created_at":"ANY STRING IS OK"}') { [200, {}, ''] }
end
connection.post('/foo', JSON.dump(name: 'YK', created_at: Time.now))
stubs.verify_stubbed_calls
```

In this case, it's difficult to make tests always pass with
`"created_at"` because the value is dynamic. So, I came up with an idea
to pass a proc as a stubbed value and compare bodies, inside the proc:

```ruby
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
  check = -> (request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
  stub.post('/foo', check) { [200, {}, ''] }
end
connection.post('/foo', JSON.dump(name: 'YK', created_at: Time.now))
stubs.verify_stubbed_calls
```

I believe this would be flexible but compatible with the previous behavior.
2022-07-28 10:03:13 +01:00
6 changed files with 120 additions and 2 deletions

View File

@ -64,6 +64,14 @@ initialized. This is useful for testing.
stubs.get('/uni') { |env| [ 200, {}, 'urchin' ]}
```
You can also stub the request body with a string or a proc.
It would be useful to pass a proc if it's OK only to check the parts of the request body are passed.
```ruby
stubs.post('/kohada', 'where=sea&temperature=24') { |env| [ 200, {}, 'spotted gizzard shad' ]}
stubs.post('/anago', -> (request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'Wakamoto' } }) { |env| [200, {}, 'conger eel'] }
```
If you want to stub requests that exactly match a path, parameters, and headers,
`strict_mode` would be useful.

View File

@ -17,6 +17,11 @@ class Client
data = JSON.parse(res.body)
data['origin']
end
def foo(params)
res = @conn.post('/foo', JSON.dump(params))
res.status
end
end
RSpec.describe Client do
@ -94,4 +99,21 @@ RSpec.describe Client do
stubs.verify_stubbed_calls
end
end
context 'When you want to test the body, you can use a proc as well as string' do
it 'tests with a string' do
stubs.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
expect(client.foo(name: 'YK')).to eq 200
stubs.verify_stubbed_calls
end
it 'tests with a proc' do
check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
stubs.post('/foo', check) { [200, {}, ''] }
expect(client.foo(name: 'YK', created_at: Time.now)).to eq 200
stubs.verify_stubbed_calls
end
end
end

View File

@ -18,6 +18,11 @@ class Client
data = JSON.parse(res.body)
data['origin']
end
def foo(params)
res = @conn.post('/foo', JSON.dump(params))
res.status
end
end
# Example API client test
@ -109,6 +114,27 @@ class ClientTest < Test::Unit::TestCase
stubs.verify_stubbed_calls
end
def test_with_string_body
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
stub.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
end
cli = client(stubs)
assert_equal 200, cli.foo(name: 'YK')
stubs.verify_stubbed_calls
end
def test_with_proc_body
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
stub.post('/foo', check) { [200, {}, ''] }
end
cli = client(stubs)
assert_equal 200, cli.foo(name: 'YK', created_at: Time.now)
stubs.verify_stubbed_calls
end
def client(stubs)
conn = Faraday.new do |builder|
builder.adapter :test, stubs

View File

@ -26,6 +26,15 @@ module Faraday
# ]
# end
#
# # Test the request body is the same as the stubbed body
# stub.post('/bar', 'name=YK&word=call') { [200, {}, ''] }
#
# # You can pass a proc as a stubbed body and check the request body in your way.
# # In this case, the proc should return true or false.
# stub.post('/foo', ->(request_body) do
# JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }) { [200, {}, '']
# end
#
# # You can set strict_mode to exactly match the stubbed requests.
# stub.strict_mode = true
# end
@ -42,6 +51,12 @@ module Faraday
#
# resp = test.get '/items/2'
# resp.body # => 'showing item: 2'
#
# resp = test.post '/bar', 'name=YK&word=call'
# resp.status # => 200
#
# resp = test.post '/foo', JSON.dump(name: 'YK', created_at: Time.now)
# resp.status # => 200
class Test < Faraday::Adapter
attr_accessor :stubs
@ -181,7 +196,7 @@ module Faraday
[(host.nil? || host == request_host) &&
path_match?(request_path, meta) &&
params_match?(env) &&
(body.to_s.size.zero? || request_body == body) &&
body_match?(request_body) &&
headers_match?(request_headers), meta]
end
@ -222,6 +237,17 @@ module Faraday
end
end
def body_match?(request_body)
return true if body.to_s.size.zero?
case body
when Proc
body.call(request_body)
else
request_body == body
end
end
def to_s
"#{path} #{body}"
end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Faraday
VERSION = '2.3.0'
VERSION = '2.4.0'
end

View File

@ -373,5 +373,41 @@ RSpec.describe Faraday::Adapter::Test do
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent' }
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent', x_special: 'special' }
end
describe 'body_match?' do
let(:stubs) do
described_class::Stubs.new do |stubs|
stubs.post('/no_check') { [200, {}, 'ok'] }
stubs.post('/with_string', 'abc') { [200, {}, 'ok'] }
stubs.post(
'/with_proc',
->(request_body) { JSON.parse(request_body, symbolize_names: true) == { x: '!', a: [{ m: [{ a: true }], n: 123 }] } },
{ content_type: 'application/json' }
) do
[200, {}, 'ok']
end
end
end
context 'when trying without any args for body' do
subject(:without_body) { connection.post('/no_check') }
it { expect(without_body.status).to eq 200 }
end
context 'when trying with string body stubs' do
subject(:with_string) { connection.post('/with_string', 'abc') }
it { expect(with_string.status).to eq 200 }
end
context 'when trying with proc body stubs' do
subject(:with_proc) do
connection.post('/with_proc', JSON.dump(a: [{ n: 123, m: [{ a: true }] }], x: '!'), { 'Content-Type' => 'application/json' })
end
it { expect(with_proc.status).to eq 200 }
end
end
end
end