faraday-hot_mock 0.3.0 → 0.4.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 +4 -4
- data/README.md +64 -15
- data/lib/faraday/hot_mock/adapter.rb +7 -42
- data/lib/faraday/hot_mock/version.rb +1 -1
- data/lib/faraday/hot_mock.rb +115 -6
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56228dde369003b5c2b7a68b7af56e2533c13d07ffc9dc997c8fe3a937c48f55
|
|
4
|
+
data.tar.gz: bc60f461818b035f6e3d14a28d6b6f9943d47bac85ce398013a91569fba6268f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6398844d597bda4844ef3967f104dd6300ff9d9226128b34df2ef2ae096e740f0037b9708f53f70acf5151405a581d507a15ff4e3e9db3fc0d590bfabcdd8694
|
|
7
|
+
data.tar.gz: 3ee86b689589bb1cf24d012a47feddefe1e2d8074497953acf7ee85d1936a02387ec3124b07b2cd366345005a5e5dc73a67507e7b63452f209e05ed226905afa
|
data/README.md
CHANGED
|
@@ -6,6 +6,17 @@ This adapter attempts to make that simpler by parsing YAML files at runtime. If
|
|
|
6
6
|
|
|
7
7
|
_**This adapter is meant for Faraday usage in Rails, not for Faraday that's used in other frameworks or situations.**_
|
|
8
8
|
|
|
9
|
+
## Why Use Faraday::HotMock instead of VCR?
|
|
10
|
+
|
|
11
|
+
VCR focuses on keeping HTTP requests out of tests - Faraday::HotMock focuses on simulating API responses during development.
|
|
12
|
+
|
|
13
|
+
To use VCR in development would require wrapping code in VCR blocks, which must then be undone before deployment. Simple, but tedious and error-prone.
|
|
14
|
+
|
|
15
|
+
Faraday::HotMock requires no code changes to the application - it doesn't mock anything in production, even if you try. So while the HotMock adapter is "used", it just passes requests to the default or adapter or specified fallback.
|
|
16
|
+
|
|
17
|
+
VCR works with any HTTP library, Faraday::HotMock only works with Faraday. This is a critical limitation unless you use only or primarily Faraday.
|
|
18
|
+
|
|
19
|
+
You could, ostensibly, replace VCR with Faraday::HotMock in tests. VCR is battle-tested, well-written and widely used, so it's likely a better choice for testing. But the goal is to make Faraday::HotMock just as useful in all non-production environments.
|
|
9
20
|
|
|
10
21
|
## How It Works
|
|
11
22
|
|
|
@@ -19,13 +30,12 @@ This means that if you have a Staging environment, or a UAT environment along wi
|
|
|
19
30
|
|
|
20
31
|
You can organize your per-environment mocks as you see fit - all in one file, or split between descriptive directories and file names.
|
|
21
32
|
|
|
22
|
-
|
|
23
33
|
## Installation
|
|
24
34
|
|
|
25
35
|
Add this line to your application's Gemfile:
|
|
26
36
|
|
|
27
37
|
```ruby
|
|
28
|
-
gem "faraday-hot_mock"
|
|
38
|
+
gem "faraday-hot_mock"
|
|
29
39
|
```
|
|
30
40
|
|
|
31
41
|
And then execute:
|
|
@@ -33,7 +43,6 @@ And then execute:
|
|
|
33
43
|
$ bundle
|
|
34
44
|
```
|
|
35
45
|
|
|
36
|
-
|
|
37
46
|
## Usage
|
|
38
47
|
|
|
39
48
|
Add this adapter to your middleware pipeline, making sure that it's last:
|
|
@@ -52,11 +61,11 @@ Optionally, specify a fallback adapter (`Faraday.default_adapter` is the default
|
|
|
52
61
|
@conn = Faraday.new(url: "https://dog.ceo/api/") do |faraday|
|
|
53
62
|
faraday.request :json
|
|
54
63
|
faraday.response :json
|
|
55
|
-
faraday.adapter :hot_mock, fallback: :cool_community_adapter
|
|
64
|
+
faraday.adapter :hot_mock, fallback: :cool_community_adapter # only useful if the default adapter isn't already :cool_community_adapter!
|
|
56
65
|
end
|
|
57
66
|
```
|
|
58
67
|
|
|
59
|
-
Then add the switch: `tmp/mocking-#{Rails.env}.txt
|
|
68
|
+
Then add the switch: `tmp/mocking-#{Rails.env}.txt` (or use the [convenience method](#convenience-methods)). Just like Rails' own `tmp/caching-dev.txt` file, this will toggle HotMock on when present, and off when not present.
|
|
60
69
|
|
|
61
70
|
> ⚠️ REMEMBER: For caching, it's `tmp/caching-dev.txt`, but for mocking it's `tmp/mocking-development.txt`
|
|
62
71
|
|
|
@@ -64,7 +73,21 @@ Now, create the directory `lib/faraday/mocks/` and a subdirectory for each envir
|
|
|
64
73
|
|
|
65
74
|
> ⚠️ REMEMBER: it's `lib/faraday/mocks`, not `app/lib/faraday/mocks`
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
### Git Ignore?
|
|
77
|
+
|
|
78
|
+
It can be useful to share mocks with others, or to ensure you don't ever lose them.
|
|
79
|
+
|
|
80
|
+
It can also be a terrible idea if your mocks are designed for your specific needs, and can cause noise in PRs and commits.
|
|
81
|
+
|
|
82
|
+
It's up to you and your team what makes sense.
|
|
83
|
+
|
|
84
|
+
In most cases, it makes sense to check in the mocks for the test environment, and ignore all the rest so in most cases you should add to your `.gitignore`:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
# Ignore Faraday HotMocks except in test env
|
|
88
|
+
lib/faraday/mocks/**
|
|
89
|
+
!lib/faraday/mocks/test
|
|
90
|
+
```
|
|
68
91
|
|
|
69
92
|
### Convenience Methods
|
|
70
93
|
|
|
@@ -76,19 +99,27 @@ Faraday::HotMock.disable! # deletes tmp/mocking-#{Rails.env}.txt
|
|
|
76
99
|
Faraday::HotMock.toggle! # creates tmp/mocking-#{Rails.env}.txt if missing, deletes if present
|
|
77
100
|
```
|
|
78
101
|
|
|
79
|
-
|
|
102
|
+
You can check if mocking is enabled with:
|
|
80
103
|
|
|
81
104
|
```ruby
|
|
82
105
|
Faraday::HotMock.enabled? # returns true/false;
|
|
83
106
|
Faraday::HotMock.disabled? # returns true/false;
|
|
84
107
|
```
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
You can check if a given url and method will match against a mock with:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
Faraday::HotMock.mocked?(method: 'GET', url: 'https://vendorname.com/api/v1/endpoint') # returns matching mock or false
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
These methods have limited use, but can be helpful in scripting scenarios, or to skip tedious file creation and deletion.
|
|
87
116
|
|
|
88
117
|
### Defining Mocks
|
|
89
118
|
|
|
119
|
+
You can simply create a YAML file in `lib/faraday/mocks/#{Rails.env}/` with one or more mock definitions by hand:
|
|
120
|
+
|
|
90
121
|
```yaml
|
|
91
|
-
# lib/faraday/mocks/development/
|
|
122
|
+
# lib/faraday/mocks/development/any_name_you_like.yml
|
|
92
123
|
- url_pattern: vendorname.com.*/endpoint
|
|
93
124
|
method: POST
|
|
94
125
|
status: 418
|
|
@@ -98,12 +129,14 @@ These methods have limited use, but can be helpful in scripting scenarios.
|
|
|
98
129
|
error: I'm a teapot
|
|
99
130
|
```
|
|
100
131
|
|
|
132
|
+
Or you can do the same in the default mock file for the current environment: `lib/faraday/mocks/#{Rails.env}/hot_mocks.yml`.
|
|
133
|
+
|
|
101
134
|
Now, any POST request made to `vendorname.com/api/v1/endpoint` will return a mock 418 response with a JSON body. A GET to the same endpoint will make the actual call.
|
|
102
135
|
|
|
103
136
|
If you edit the file to be:
|
|
104
137
|
|
|
105
138
|
```yaml
|
|
106
|
-
# lib/faraday/mocks/development/
|
|
139
|
+
# lib/faraday/mocks/development/any_name_you_like.yml
|
|
107
140
|
- url_pattern: vendorname.com.*/endpoint
|
|
108
141
|
method: POST
|
|
109
142
|
status: 503
|
|
@@ -115,7 +148,27 @@ If you edit the file to be:
|
|
|
115
148
|
|
|
116
149
|
then the next request made to `vendorname.com/api/v1/endpoint` will return a mock 503 response with a JSON body. No need to reload anything.
|
|
117
150
|
|
|
118
|
-
|
|
151
|
+
If you want to add a mock from the Rails console, you can use `Faraday::HotMock.mock!(method: [method], url_pattern: [url_pattern], status: [status], headers: [headers], body: [body])`. This will add the mock to the default mock file for the current environment.
|
|
152
|
+
|
|
153
|
+
A mock can be recorded by calling `Faraday::HotMock.record(method: [method], url: [url])`. This will make the actual call and then store the response in the default mock file for the current environment: `lib/faraday/mocks/#{Rails.env}/hot_mocks.yml`.
|
|
154
|
+
|
|
155
|
+
If a mock already exists for that method and URL, no request will be made and `false` will be returned.
|
|
156
|
+
|
|
157
|
+
Use `Faraday::HotMock.record!` (with a bang) to force recording even if a mock already exists. This will remove the old matching mock and put the new one in its place.
|
|
158
|
+
|
|
159
|
+
> ⚠️ WARNING: Recording does not remove any mocks from custom files, only from the default `hot_mocks.yml` file. If there are duplicates between custom files and the default file, there is no guarantee which one will be used.
|
|
160
|
+
|
|
161
|
+
Once recorded, you can easily edit the mock file to change status codes, headers, or body content as needed.
|
|
162
|
+
|
|
163
|
+
If you need to know where the mock file is located, you can call `Faraday::HotMock.hot_mock_file`, which will return the full path to the default mock file for the current environment.
|
|
164
|
+
|
|
165
|
+
### REGEX and You(r Mocks)
|
|
166
|
+
|
|
167
|
+
When recording a mock, you must pass a full URL. However, when defining mocks in YAML files, you can use regular expressions for more flexible matching.
|
|
168
|
+
|
|
169
|
+
Remember that once recorded, the `url_pattern` can be adjusted.
|
|
170
|
+
|
|
171
|
+
### Disabling Mocks
|
|
119
172
|
|
|
120
173
|
If you want to disable mocks, you can:
|
|
121
174
|
|
|
@@ -127,14 +180,10 @@ If you want to disable mocks, you can:
|
|
|
127
180
|
- Delete the directory
|
|
128
181
|
- Use the [convenience methods](#convenience-methods)
|
|
129
182
|
|
|
130
|
-
If you'd rather keep the file(s) around, just delete `tmp/mocking-development.txt`. That will globally disable any mocked responses.
|
|
131
|
-
|
|
132
|
-
|
|
133
183
|
## Contributing
|
|
134
184
|
|
|
135
185
|
Fork, work, PR while following the [Code of Conduct](https://github.com/seanhogge/faraday-hot_mock/CODE_OF_CONDUCT.md)
|
|
136
186
|
|
|
137
|
-
|
|
138
187
|
## License
|
|
139
188
|
|
|
140
189
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -9,60 +9,25 @@ module Faraday
|
|
|
9
9
|
class Adapter < Faraday::Adapter
|
|
10
10
|
def initialize(app, options = {})
|
|
11
11
|
super(app)
|
|
12
|
-
@mock_dir = options[:mock_dir] || default_mock_dir
|
|
13
12
|
@enabled_file = options[:enabled_file] || "tmp/mocking-#{Rails.env}.txt"
|
|
14
13
|
fallback = options[:fallback] || Faraday.default_adapter
|
|
15
14
|
@fallback_adapter = Faraday::Adapter.lookup_middleware(fallback)
|
|
16
|
-
@mocks =
|
|
15
|
+
@mocks = Faraday::HotMock.mocks
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def call(env)
|
|
20
19
|
super
|
|
21
|
-
if mocking_enabled? && @mocks && (mock = find_mock(env.method, env.url))
|
|
22
|
-
save_response(env, mock["status"] || 200, mock["body"] || "", mock["headers"] || {})
|
|
23
|
-
else
|
|
24
|
-
@fallback_adapter.new(@app, @options).call(env)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
mocks = []
|
|
34
|
-
|
|
35
|
-
yaml_files.each do |file|
|
|
36
|
-
file_mocks = YAML.load_file(file)
|
|
37
|
-
mocks.concat(file_mocks) if file_mocks.is_a?(Array)
|
|
21
|
+
if Rails.env.production?
|
|
22
|
+
return @fallback_adapter.new(@app, @options).call(env)
|
|
38
23
|
end
|
|
39
24
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
File.exist?(Rails.root.join @enabled_file)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def default_mock_dir
|
|
48
|
-
rails_env = defined?(Rails) ? Rails.env : ENV["RAILS_ENV"] || "development"
|
|
49
|
-
"lib/faraday/mocks/#{rails_env}"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def find_mock(method, url)
|
|
53
|
-
return nil unless @mocks.any?
|
|
54
|
-
|
|
55
|
-
@mocks.find do |mock|
|
|
56
|
-
url_matches = Regexp.new(mock["url_pattern"]).match?(url.to_s)
|
|
57
|
-
method_matches = mock["method"].nil? || mock["method"].to_s.upcase == method.to_s.upcase
|
|
58
|
-
|
|
59
|
-
url_matches && method_matches
|
|
25
|
+
if Faraday::HotMock.enabled? && (mock = Faraday::HotMock.mocked?(method: env.method, url: env.url))
|
|
26
|
+
save_response(env, mock["status"] || 200, mock["body"] || "", mock["headers"] || {})
|
|
27
|
+
else
|
|
28
|
+
@fallback_adapter.new(@app, @options).call(env)
|
|
60
29
|
end
|
|
61
30
|
end
|
|
62
|
-
|
|
63
|
-
def yaml_files
|
|
64
|
-
Dir.glob(File.join(Rails.root.join(@mock_dir), "**", "*.{yml,yaml}"))
|
|
65
|
-
end
|
|
66
31
|
end
|
|
67
32
|
end
|
|
68
33
|
end
|
data/lib/faraday/hot_mock.rb
CHANGED
|
@@ -5,31 +5,140 @@ require "faraday"
|
|
|
5
5
|
|
|
6
6
|
module Faraday
|
|
7
7
|
module HotMock
|
|
8
|
-
|
|
8
|
+
extend self
|
|
9
9
|
|
|
10
10
|
def disable!
|
|
11
|
-
FileUtils.rm_f(
|
|
11
|
+
FileUtils.rm_f(hot_mocking_file)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def disabled?
|
|
15
|
-
!File.exist?(
|
|
15
|
+
!File.exist?(hot_mocking_file)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def enable!
|
|
19
|
-
FileUtils.touch(
|
|
19
|
+
FileUtils.touch(hot_mocking_file)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def enabled?
|
|
23
|
-
File.exist?(
|
|
23
|
+
File.exist?(hot_mocking_file)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def toggle!
|
|
27
|
-
if File.exist?(
|
|
27
|
+
if File.exist?(hot_mocking_file)
|
|
28
28
|
disable!
|
|
29
29
|
else
|
|
30
30
|
enable!
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
def delete_mock(method:, url:)
|
|
35
|
+
return unless File.exist?(hot_mock_file)
|
|
36
|
+
|
|
37
|
+
mocks = YAML.load_file(hot_mock_file) || []
|
|
38
|
+
|
|
39
|
+
mocks.reject! { |entry| entry["url_pattern"] == url && entry["method"].to_s.upcase == method.to_s.upcase }
|
|
40
|
+
|
|
41
|
+
File.write(hot_mock_file, mocks.to_yaml)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def mock!(method:, url:, status:, headers: {}, body: nil)
|
|
45
|
+
FileUtils.touch(hot_mock_file)
|
|
46
|
+
|
|
47
|
+
mocks = YAML.load_file(hot_mock_file) || []
|
|
48
|
+
|
|
49
|
+
mocks.reject! { |entry| entry["url_pattern"] == url && entry["method"].to_s.upcase == method.to_s.upcase }
|
|
50
|
+
|
|
51
|
+
mocks << {
|
|
52
|
+
"method" => method.to_s.upcase,
|
|
53
|
+
"url_pattern" => url,
|
|
54
|
+
"status" => status,
|
|
55
|
+
"headers" => headers,
|
|
56
|
+
"body" => body
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
File.write(hot_mock_file, mocks.to_yaml)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mocked?(method:, url:)
|
|
63
|
+
mocks.find { |entry| entry["method"].to_s.upcase == method.to_s.upcase && Regexp.new(entry["url_pattern"]).match?(url.to_s) } || false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def record(method:, url:)
|
|
67
|
+
return false if mocked?(method:, url:)
|
|
68
|
+
|
|
69
|
+
faraday = Faraday.new
|
|
70
|
+
|
|
71
|
+
response = faraday.send(method.downcase.to_sym, url)
|
|
72
|
+
|
|
73
|
+
FileUtils.touch(hot_mock_file)
|
|
74
|
+
|
|
75
|
+
hot_mocks = YAML.load_file(hot_mock_file) || []
|
|
76
|
+
|
|
77
|
+
hot_mocks << {
|
|
78
|
+
"method" => method.to_s.upcase,
|
|
79
|
+
"url_pattern" => url,
|
|
80
|
+
"status" => response.status,
|
|
81
|
+
"headers" => response.headers.to_h.merge("x-hotmock-recorded-at" => Time.now.utc.iso8601),
|
|
82
|
+
"body" => response.body
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
File.write(hot_mock_file, hot_mocks.to_yaml)
|
|
86
|
+
rescue Faraday::Error => e
|
|
87
|
+
puts "Error recording mock for #{method.upcase} #{url}: #{e.message}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def record!(method:, url:)
|
|
91
|
+
faraday = Faraday.new
|
|
92
|
+
|
|
93
|
+
response = faraday.send(method.downcase.to_sym, url)
|
|
94
|
+
|
|
95
|
+
FileUtils.touch(hot_mock_file)
|
|
96
|
+
|
|
97
|
+
hot_mocks = YAML.load_file(hot_mock_file) || []
|
|
98
|
+
|
|
99
|
+
hot_mocks.reject! { |entry| entry["url_pattern"] == url && entry["method"].to_s.upcase == method.to_s.upcase }
|
|
100
|
+
|
|
101
|
+
hot_mocks << {
|
|
102
|
+
"method" => method.to_s.upcase,
|
|
103
|
+
"url_pattern" => url,
|
|
104
|
+
"status" => response.status,
|
|
105
|
+
"headers" => response.headers.to_h.merge("x-hotmock-recorded-at" => Time.now.utc.iso8601),
|
|
106
|
+
"body" => response.body
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
File.write(hot_mock_file, hot_mocks.to_yaml)
|
|
110
|
+
rescue Faraday::Error => e
|
|
111
|
+
puts "Error recording mock for #{method.upcase} #{url}: #{e.message}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def hot_mock_dir
|
|
115
|
+
Rails.root.join "lib/faraday/mocks/#{Rails.env}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def hot_mocking_file
|
|
119
|
+
Rails.root.join "tmp/mocking-#{Rails.env}.txt"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def hot_mock_file
|
|
123
|
+
Rails.root.join(hot_mock_dir, "hot_mocks.yml")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def mocks
|
|
127
|
+
return [] unless Dir.exist?(hot_mock_dir)
|
|
128
|
+
|
|
129
|
+
mocks = []
|
|
130
|
+
|
|
131
|
+
all_hot_mock_files.each do |file|
|
|
132
|
+
file_mocks = YAML.load_file(file)
|
|
133
|
+
mocks.concat(file_mocks) if file_mocks.is_a?(Array)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
mocks
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def all_hot_mock_files
|
|
140
|
+
Dir.glob(File.join(hot_mock_dir, "**", "*.{yml,yaml}"))
|
|
141
|
+
end
|
|
33
142
|
end
|
|
34
143
|
end
|
|
35
144
|
|