Skip to content

Commit

Permalink
Merge pull request #10 from nepalez/v0.1.0
Browse files Browse the repository at this point in the history
Add stubbing of http requests via webmock
  • Loading branch information
nepalez committed Feb 9, 2020
2 parents 2099fdc + 89db441 commit fa305be
Show file tree
Hide file tree
Showing 15 changed files with 289 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ AllCops:
StyleGuideCopsOnly: true
TargetRubyVersion: 2.3

Metrics/LineLength:
Layout/LineLength:
AllowHeredoc: true
AllowURI: true
URISchemes:
Expand Down
8 changes: 1 addition & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,8 @@ script:
- bundle exec rubocop
rvm:
- 2.3.0
- 2.6.0
- jruby-9.2.0.0
- 2.7.0
- ruby-head
- jruby-head
env:
global:
- JRUBY_OPTS='--dev -J-Xmx1024M'
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.1.0] - To be released

### Added

- Stubbing of the HTTP requests using webmock (nepalez)

```yaml
---
- url: example.com/foo
method: get
body: foobar
query:
foo: bar
basic_auth:
user: foo
password: bar
headers:
Accept: utf-8
responses:
- status: 200
body: foobar
- status: 404
```
## [0.0.7] - [2019-07-01]
### Added
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,25 @@ For message chains:
- `arguments` (optional) for specific arguments
- `actions` for an array of actions for consecutive invocations of the chain

Every action either `return` some value, or `raise` some exception

For constants:

- `const` for stubbed constant
- `value` for a value of the constant

Every action either `return` some value, or `raise` some exception
For http requests:

- `url` or `uri` for the URI of the request (treats values like `/.../` as regular expressions)
- `method` for the specific http-method (like `get` or `post`)
- `body` for the request body (treats values like `/.../` as regular expressions)
- `headers` for the request headers
- `query` for the request query
- `basic_auth` for the `user` and `password` of basic authentication
- `response` or `responses` for consecutively envoked responses with keys:
- `status`
- `body`
- `headers`

```yaml
# ./stubs.yml
Expand Down Expand Up @@ -170,6 +183,21 @@ Every action either `return` some value, or `raise` some exception
- const: NOTIFIER_TIMEOUT_SEC
value: 10
# Examples for stubbing HTTP
- uri: /example.com/foo/ # regexp!
method: delete
basic_auth:
user: foo
password: bar
responses:
- status: 200 # for the first call
- status: 404 # for any other call
- uri: htpps://example.com/foo # exact string!
method: delete
responses:
- status: 401
```

```graphql
Expand Down
7 changes: 4 additions & 3 deletions fixturama.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |gem|
gem.name = "fixturama"
gem.version = "0.0.7"
gem.version = "0.1.0"
gem.author = "Andrew Kozin (nepalez)"
gem.email = "[email protected]"
gem.homepage = "https://github.com/nepalez/fixturama"
Expand All @@ -15,9 +15,10 @@ Gem::Specification.new do |gem|

gem.add_runtime_dependency "factory_bot", "~> 4.0"
gem.add_runtime_dependency "rspec", "~> 3.0"
gem.add_runtime_dependency "hashie", "~> 3.6"
gem.add_runtime_dependency "hashie", "~> 3.0"
gem.add_runtime_dependency "webmock", "~> 3.0"

gem.add_development_dependency "rake", "~> 10"
gem.add_development_dependency "rspec-its", "~> 1.2"
gem.add_development_dependency "rspec-its", "~> 1.0"
gem.add_development_dependency "rubocop", "~> 0.49"
end
1 change: 1 addition & 0 deletions lib/fixturama.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "hashie/mash"
require "json"
require "rspec"
require "webmock/rspec"
require "yaml"

module Fixturama
Expand Down
28 changes: 25 additions & 3 deletions lib/fixturama/stubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Fixturama
class Stubs
require_relative "stubs/chain"
require_relative "stubs/const"
require_relative "stubs/request"

#
# Register new action and apply the corresponding stub
Expand Down Expand Up @@ -35,21 +36,42 @@ def find_or_create_stub!(options)
stub = case stub_type(options)
when :message_chain then Chain.new(options)
when :constant then Const.new(options)
when :request then Request.new(options)
end

@stubs[stub.to_s] ||= stub if stub
@stubs[stub.key] ||= stub if stub
end

def stub_type(options)
return :message_chain if options[:class] || options[:object]
return :constant if options[:const]
key = (TYPES.keys & options.keys).first
return TYPES[key] if key

raise ArgumentError, <<~MESSAGE
Cannot figure out what to stub from #{options}.
You should define either a class and a message chain, or some const.
MESSAGE
end

# Matches keys to the type of the stub
TYPES = {
arguments: :message_chain,
actions: :message_chain,
basic_auth: :request,
body: :request,
chain: :message_chain,
class: :message_chain,
const: :constant,
headers: :request,
http_method: :request,
object: :message_chain,
query: :request,
response: :request,
responses: :request,
uri: :request,
url: :request,
value: :constant,
}.freeze

def symbolize(options)
Hash(options).transform_keys { |key| key.to_s.to_sym }
end
Expand Down
1 change: 1 addition & 0 deletions lib/fixturama/stubs/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def to_s
"#{receiver}.#{messages.join(".")}"
end
alias to_str to_s
alias key to_s

#
# Register new action for some arguments
Expand Down
1 change: 1 addition & 0 deletions lib/fixturama/stubs/const.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def to_s
const.to_s
end
alias to_str to_s
alias key to_s

#
# Overload the definition for the constant
Expand Down
98 changes: 98 additions & 0 deletions lib/fixturama/stubs/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#
# Stubbed request
#
class Fixturama::Stubs::Request
require_relative "request/response"
require_relative "request/responses"

def to_s
"#{http_method.upcase} #{uri.to_s == "" ? "*" : uri}"
end
alias to_str to_s

# every stub is unique
alias key hash
def update!(_); end

def apply!(example)
stub = example.stub_request(http_method, uri)
stub = stub.with(request) if request.any?
stub.to_return { |_| responses.next }
end

private

attr_reader :options

def initialize(options)
@options = options
with_error { @options = Hash(options).symbolize_keys }
end

HTTP_METHODS = %i[get post put patch delete head options any].freeze

def http_method
value = with_error("method") { options[:method]&.to_sym&.downcase } || :any
return value if HTTP_METHODS.include?(value)

raise ArgumentError, "Invalid HTTP method #{value} in #{@optons}"
end

def uri
with_error("uri") { maybe_regexp(options[:uri] || options[:url]) }
end

def headers
with_error("headers") do
Hash(options[:headers]).transform_keys(&:to_s) if options.key?(:headers)
end
end

def query
with_error("query") do
Hash(options[:query]).transform_keys(&:to_s) if options.key?(:query)
end
end

def body
with_error("body") do
case options[:body]
when NilClass then nil
when Hash then options[:body]
else maybe_regexp(options[:body])
end
end
end

def basic_auth
with_error("basic auth") do
value = options[:auth] || options[:basic_auth]
Hash(value).transform_keys(&:to_s).values_at("user", "pass") if value
end
end

def request
@request ||= {
headers: headers,
body: body,
query: query,
basic_auth: basic_auth
}.select { |_, val| val }
end

def responses
@responses ||= Responses.new(options[:response] || options[:responses])
end

def with_error(part = nil)
yield
rescue RuntimeError
message = ["Cannot extract a request", part, "from #{options}"].join(" ")
raise ArgumentError, message, __FILE__, __LINE__ - 1
end

def maybe_regexp(str)
str = str.to_s
str[%r{\A/.*/\z}] ? Regexp.new(str[1..-2]) : str
end
end
43 changes: 43 additions & 0 deletions lib/fixturama/stubs/request/response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class Fixturama::Stubs::Request
class Response
def to_h
{ status: status, body: body, headers: headers }.select { |_, val| val }
end

private

def initialize(options)
@options = options
@options = with_error { Hash(options).transform_keys(&:to_sym) }
end

attr_reader :options

def status
with_error("status") { options[:status]&.to_i } || 200
end

def body
with_error("body") do
case options[:body]
when NilClass then nil
when Hash then JSON.dump(options[:body])
else options[:body].to_s
end
end
end

def headers
with_error("headers") do
Hash(options[:headers]).map { |k, v| [k.to_s, v.to_s] }.to_h
end
end

def with_error(part = nil)
yield
rescue RuntimeError
text = ["Cannot extract a response", part, "from #{options}"].join(" ")
raise ArgumentError, text, __FILE__, __LINE__ - 1
end
end
end
20 changes: 20 additions & 0 deletions lib/fixturama/stubs/request/responses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Fixturama::Stubs::Request
class Responses
def next
list.count > @count ? list[@count].tap { @count += 1 } : list.last
end

private

def initialize(list)
@count = 0
list ||= [{ status: 200 }]
@list = case list
when Array then list.map { |item| Response.new(item).to_h }
else [Response.new(list).to_h]
end
end

attr_reader :list
end
end
5 changes: 4 additions & 1 deletion spec/fixturama/seed_fixture/_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@

it "runs the factory", aggregate_failures: true do
expect(FactoryBot).to receive(:create_list).with(:foo, 1, :baz, qux: 42)
expect(FactoryBot).to receive(:create_list).with(:foo, 3, :bar, {})
expect(FactoryBot).to receive(:create_list) do |*args, **opts|
expect(args).to eq [:foo, 3, :bar]
expect(opts).to be_empty
end

subject
end
Expand Down
Loading

0 comments on commit fa305be

Please sign in to comment.