fixturama 0.1.0 → 0.2.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/CHANGELOG.md +29 -0
- data/README.md +32 -4
- data/fixturama.gemspec +1 -1
- data/lib/fixturama.rb +48 -20
- data/lib/fixturama/changes.rb +71 -0
- data/lib/fixturama/changes/base.rb +28 -0
- data/lib/fixturama/changes/chain.rb +64 -0
- data/lib/fixturama/changes/chain/actions.rb +31 -0
- data/lib/fixturama/changes/chain/arguments.rb +59 -0
- data/lib/fixturama/changes/chain/raise_action.rb +35 -0
- data/lib/fixturama/changes/chain/return_action.rb +33 -0
- data/lib/fixturama/changes/const.rb +30 -0
- data/lib/fixturama/changes/request.rb +91 -0
- data/lib/fixturama/changes/request/response.rb +62 -0
- data/lib/fixturama/changes/request/responses.rb +22 -0
- data/lib/fixturama/changes/seed.rb +39 -0
- data/lib/fixturama/config.rb +5 -0
- data/lib/fixturama/fixture_error.rb +31 -0
- data/lib/fixturama/loader.rb +1 -0
- data/lib/fixturama/loader/context.rb +1 -0
- data/lib/fixturama/loader/value.rb +1 -0
- data/spec/fixturama/seed_fixture/_spec.rb +1 -0
- data/spec/fixturama/seed_fixture/seed.yml +0 -5
- metadata +34 -34
- data/lib/fixturama/seed.rb +0 -15
- data/lib/fixturama/stubs.rb +0 -79
- data/lib/fixturama/stubs/chain.rb +0 -90
- data/lib/fixturama/stubs/chain/actions.rb +0 -39
- data/lib/fixturama/stubs/chain/actions/raise.rb +0 -21
- data/lib/fixturama/stubs/chain/actions/return.rb +0 -23
- data/lib/fixturama/stubs/chain/arguments.rb +0 -86
- data/lib/fixturama/stubs/const.rb +0 -43
- data/lib/fixturama/stubs/request.rb +0 -98
- data/lib/fixturama/stubs/request/response.rb +0 -43
- data/lib/fixturama/stubs/request/responses.rb +0 -20
- data/lib/fixturama/utils.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed87b651886697aaea8b0a79ddbf5b875372ca382926f3dc07d8f3bec2a4d07d
|
4
|
+
data.tar.gz: 76248c5bb6b75d335c80387d11b6446cee23829bf1ca8f6fb319c12a1b5f0e52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20607f5c8bc2ed14ac8bef4bf207495946a02f4a39e2d6d77661b4fa154c01b3f4072e62aaf9083509369eb4b9fe01b2c9dfe1aa4e87b7bf2bb9a3b31b0a1827
|
7
|
+
data.tar.gz: baf0b2716a960d8885dda3dde622009ebf8a1335912358898283b42b7beea04137e9d2ee1bcc58d305b8cdf14c21f3bdfa4442674a4dbb780aad61486b0e2682
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
7
|
|
8
|
+
## [0.2.0] - [2020-02-17]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Stubbing and seeding from the same source file via the `call_fixture` method (nepalez)
|
13
|
+
|
14
|
+
```yaml
|
15
|
+
# ./changes.yml
|
16
|
+
---
|
17
|
+
- type: user
|
18
|
+
params:
|
19
|
+
id: 1
|
20
|
+
|
21
|
+
- const: DEFAULT_USER_ID
|
22
|
+
value: 1
|
23
|
+
|
24
|
+
- url: https://example.com/users/default
|
25
|
+
method: get
|
26
|
+
responses:
|
27
|
+
- body:
|
28
|
+
id: 1
|
29
|
+
name: Andrew
|
30
|
+
```
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
before { call_fixture "#{__dir__}/changes.yml" }
|
34
|
+
```
|
35
|
+
|
8
36
|
## [0.1.0] - [2020-02-09]
|
9
37
|
|
10
38
|
### Added
|
@@ -200,3 +228,4 @@ This is a first public release with features extracted from production app.
|
|
200
228
|
[0.0.6]: https://github.com/nepalez/fixturama/compare/v0.0.5...v0.0.6
|
201
229
|
[0.0.7]: https://github.com/nepalez/fixturama/compare/v0.0.6...v0.0.7
|
202
230
|
[0.1.0]: https://github.com/nepalez/fixturama/compare/v0.0.7...v0.1.0
|
231
|
+
[0.2.0]: https://github.com/nepalez/fixturama/compare/v0.1.0...v0.2.0
|
data/README.md
CHANGED
@@ -124,16 +124,17 @@ Use the `count: 2` key to create more objects at once.
|
|
124
124
|
|
125
125
|
### Stubbing
|
126
126
|
|
127
|
-
|
127
|
+
The gem supports stubbing message chains, constants and http requests with the following keys.
|
128
128
|
|
129
129
|
For message chains:
|
130
130
|
|
131
131
|
- `class` for stubbed class
|
132
132
|
- `chain` for messages chain
|
133
133
|
- `arguments` (optional) for specific arguments
|
134
|
-
- `actions` for an array of actions for consecutive invocations of the chain
|
135
|
-
|
136
|
-
|
134
|
+
- `actions` for an array of actions for consecutive invocations of the chain with keys
|
135
|
+
- `return` for a value to be returned
|
136
|
+
- `raise` for an exception to be risen
|
137
|
+
- `repeate` for a number of invocations with this action
|
137
138
|
|
138
139
|
For constants:
|
139
140
|
|
@@ -152,6 +153,7 @@ For http requests:
|
|
152
153
|
- `status`
|
153
154
|
- `body`
|
154
155
|
- `headers`
|
156
|
+
- `repeate` for the number of times this response should be returned before switching to the next one
|
155
157
|
|
156
158
|
```yaml
|
157
159
|
# ./stubs.yml
|
@@ -179,6 +181,7 @@ For http requests:
|
|
179
181
|
- <%= profile_id %>
|
180
182
|
actions:
|
181
183
|
- return: true
|
184
|
+
repeate: 1 # this is the default value
|
182
185
|
- raise: ActiveRecord::RecordNotFound
|
183
186
|
|
184
187
|
- const: NOTIFIER_TIMEOUT_SEC
|
@@ -192,6 +195,7 @@ For http requests:
|
|
192
195
|
password: bar
|
193
196
|
responses:
|
194
197
|
- status: 200 # for the first call
|
198
|
+
repeate: 1 # this is the default value, but you can set another one
|
195
199
|
- status: 404 # for any other call
|
196
200
|
|
197
201
|
- uri: htpps://example.com/foo # exact string!
|
@@ -231,6 +235,30 @@ I find it especially helpful when I need to check different edge cases. Instead
|
|
231
235
|
|
232
236
|
Looking at the spec I can easily figure out the "structure" of expectation, while looking at fixtures I can check the concrete corner cases.
|
233
237
|
|
238
|
+
## Single Source of Changes
|
239
|
+
|
240
|
+
If you will, you can list all stubs and seeds at the one single file like
|
241
|
+
|
242
|
+
```yaml
|
243
|
+
# ./changes.yml
|
244
|
+
---
|
245
|
+
- type: user
|
246
|
+
params:
|
247
|
+
id: 1
|
248
|
+
name: Andrew
|
249
|
+
|
250
|
+
- const: DEFAULT_USER_ID
|
251
|
+
value: 1
|
252
|
+
```
|
253
|
+
|
254
|
+
This fixture can be applied via `call_fixture` method just like we did above with `seed_fixture` and `stub_fixture`:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
before { call_fixture "#{__dir__}/changes.yml" }
|
258
|
+
```
|
259
|
+
|
260
|
+
In fact, since the `v0.2.0` all those methods are just the aliases of the `call_fixture`.
|
261
|
+
|
234
262
|
## License
|
235
263
|
|
236
264
|
The gem is available as open source under the terms of the [MIT License][license].
|
data/fixturama.gemspec
CHANGED
data/lib/fixturama.rb
CHANGED
@@ -6,44 +6,72 @@ require "rspec"
|
|
6
6
|
require "webmock/rspec"
|
7
7
|
require "yaml"
|
8
8
|
|
9
|
+
#
|
10
|
+
# A set of helpers to prettify specs with fixtures
|
11
|
+
#
|
9
12
|
module Fixturama
|
13
|
+
require_relative "fixturama/fixture_error"
|
10
14
|
require_relative "fixturama/config"
|
11
|
-
require_relative "fixturama/utils"
|
12
15
|
require_relative "fixturama/loader"
|
13
|
-
require_relative "fixturama/
|
14
|
-
require_relative "fixturama/seed"
|
16
|
+
require_relative "fixturama/changes"
|
15
17
|
|
18
|
+
# Set the initial value for database-generated IDs
|
19
|
+
# @param [#to_i] value
|
20
|
+
# @return [Fixturama]
|
16
21
|
def self.start_ids_from(value)
|
17
22
|
Config.start_ids_from(value)
|
23
|
+
self
|
18
24
|
end
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
# @!method read_fixture(path, options)
|
27
|
+
# Read the text content of the fixture
|
28
|
+
# @param [#to_s] path The path to the fixture file
|
29
|
+
# @param [Hash<Symbol, _>] options
|
30
|
+
# The list of options to be accessible in the fixture
|
31
|
+
# @return [String]
|
32
|
+
def read_fixture(path, **options)
|
33
|
+
content = File.read(path)
|
34
|
+
hashie = Hashie::Mash.new(options)
|
35
|
+
bindings = hashie.instance_eval { binding }
|
23
36
|
|
24
|
-
|
25
|
-
fixturama_stubs.apply(self)
|
37
|
+
ERB.new(content).result(bindings)
|
26
38
|
end
|
27
39
|
|
28
|
-
|
29
|
-
|
40
|
+
# @!method load_fixture(path, options)
|
41
|
+
# Load data from a fixture
|
42
|
+
# @param (see #read_fixture)
|
43
|
+
# @return [Object]
|
44
|
+
def load_fixture(path, **options)
|
45
|
+
Loader.new(path, options).call
|
30
46
|
end
|
31
47
|
|
32
|
-
|
33
|
-
|
48
|
+
# @!method call_fixture(path, options)
|
49
|
+
# Stub different objects and seed the database from a fixture
|
50
|
+
# @param (see #read_fixture)
|
51
|
+
# @return [RSpec::Core::Example] the current example
|
52
|
+
def call_fixture(path, **options)
|
53
|
+
items = Array load_fixture(path, **options)
|
54
|
+
items.each { |item| changes.add(item) }
|
55
|
+
tap { changes.call(self) }
|
56
|
+
rescue FixtureError => err
|
57
|
+
raise err.with_file(path)
|
34
58
|
end
|
35
59
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
60
|
+
# @!method seed_fixture(path, options)
|
61
|
+
# The alias for the +call_fixture+
|
62
|
+
# @param (see #call_fixture)
|
63
|
+
# @return (see #call_fixture)
|
64
|
+
alias seed_fixture call_fixture
|
40
65
|
|
41
|
-
|
42
|
-
|
66
|
+
# @!method stub_fixture(path, options)
|
67
|
+
# The alias for the +call_fixture+
|
68
|
+
# @param (see #call_fixture)
|
69
|
+
# @return (see #call_fixture)
|
70
|
+
alias stub_fixture call_fixture
|
43
71
|
|
44
72
|
private
|
45
73
|
|
46
|
-
def
|
47
|
-
@
|
74
|
+
def changes
|
75
|
+
@changes ||= Changes.new
|
48
76
|
end
|
49
77
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Fixturama
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# Registry of changes (stubs and seeds)
|
5
|
+
#
|
6
|
+
class Changes
|
7
|
+
require_relative "changes/base"
|
8
|
+
require_relative "changes/chain"
|
9
|
+
require_relative "changes/const"
|
10
|
+
require_relative "changes/request"
|
11
|
+
require_relative "changes/seed"
|
12
|
+
|
13
|
+
# Match option keys to the type of an item
|
14
|
+
TYPES = {
|
15
|
+
actions: Chain,
|
16
|
+
arguments: Chain,
|
17
|
+
basic_auth: Request,
|
18
|
+
body: Request,
|
19
|
+
chain: Chain,
|
20
|
+
class: Chain,
|
21
|
+
const: Const,
|
22
|
+
count: Seed,
|
23
|
+
headers: Request,
|
24
|
+
http_method: Request,
|
25
|
+
object: Chain,
|
26
|
+
params: Seed,
|
27
|
+
query: Request,
|
28
|
+
response: Request,
|
29
|
+
responses: Request,
|
30
|
+
traits: Seed,
|
31
|
+
type: Seed,
|
32
|
+
uri: Request,
|
33
|
+
url: Request,
|
34
|
+
value: Const,
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# Adds new change to the registry
|
38
|
+
# @param [Hash] options
|
39
|
+
# @return [Fixturama::Changes]
|
40
|
+
# @raise [Fixturama::FixtureError] if the options cannot be processed
|
41
|
+
def add(options)
|
42
|
+
options = Hash(options).transform_keys(&:to_sym)
|
43
|
+
types = options.keys.map { |key| TYPES[key] }.compact.uniq
|
44
|
+
raise "Wrong count" unless types.count == 1
|
45
|
+
|
46
|
+
@changes << types.first.new(options)
|
47
|
+
self
|
48
|
+
rescue FixtureError => err
|
49
|
+
raise err
|
50
|
+
rescue StandardError => err
|
51
|
+
raise FixtureError.new("an operation", options, err)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Apply all registered changes to the RSpec example
|
55
|
+
# @param [RSpec::Core::Example] example
|
56
|
+
# @return [self]
|
57
|
+
def call(example)
|
58
|
+
@changes
|
59
|
+
.group_by(&:key)
|
60
|
+
.values
|
61
|
+
.map { |changes| changes.reduce :merge }
|
62
|
+
.each { |change| change.call(example) }
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
@changes = []
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Fixturama::Changes
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# @abstract
|
5
|
+
# Base class for changes downloaded from a fixture
|
6
|
+
#
|
7
|
+
class Base
|
8
|
+
# @!attribute [r] key The key identifier of the change
|
9
|
+
# @return [String]
|
10
|
+
alias key hash
|
11
|
+
|
12
|
+
# Merge the other change into the current one
|
13
|
+
# @param [Fixturama::Changes::Base] other
|
14
|
+
# @return [Fixturama::Changes::Base]
|
15
|
+
def merge(other)
|
16
|
+
# By default just take the other change if applicable
|
17
|
+
other.class == self.class && other.key == key ? other : self
|
18
|
+
end
|
19
|
+
|
20
|
+
# @abstract
|
21
|
+
# Call the corresponding change (either a stub or a seed)
|
22
|
+
# @param [RSpec::Core::Example] _example The RSpec example
|
23
|
+
# @return [self]
|
24
|
+
def call(_example)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Fixturama::Changes
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# Stub a chain of messages
|
5
|
+
#
|
6
|
+
class Chain < Base
|
7
|
+
require_relative "chain/raise_action"
|
8
|
+
require_relative "chain/return_action"
|
9
|
+
require_relative "chain/actions"
|
10
|
+
require_relative "chain/arguments"
|
11
|
+
|
12
|
+
def key
|
13
|
+
@key ||= ["chain", @receiver.name, *@messages].join(".")
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(other)
|
17
|
+
return self unless other.class == self.class && other.key == key
|
18
|
+
|
19
|
+
tap { @arguments = (other.arguments | arguments).sort_by(&:order) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(example)
|
23
|
+
call_action = example.send(:receive_message_chain, *@messages) do |*real|
|
24
|
+
action = arguments.find { |expected| expected.match?(*real) }
|
25
|
+
action ? action.call : raise("Unexpected arguments: #{real}")
|
26
|
+
end
|
27
|
+
|
28
|
+
example.send(:allow, @receiver).to call_action
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
attr_reader :arguments
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def initialize(**options)
|
38
|
+
@receiver = receiver_from(options)
|
39
|
+
@messages = messages_from(options)
|
40
|
+
@arguments = [Arguments.new(options)]
|
41
|
+
end
|
42
|
+
|
43
|
+
def receiver_from(options)
|
44
|
+
case options.slice(:class, :object).keys
|
45
|
+
when %i[class] then Kernel.const_get(options[:class])
|
46
|
+
when %i[object] then Object.send(:eval, options[:object])
|
47
|
+
else raise
|
48
|
+
end
|
49
|
+
rescue StandardError => err
|
50
|
+
raise Fixturama::FixtureError.new("a stabbed object", options, err)
|
51
|
+
end
|
52
|
+
|
53
|
+
def messages_from(options)
|
54
|
+
case value = options[:chain]
|
55
|
+
when Array then value.map(&:to_sym)
|
56
|
+
when String then [value.to_sym]
|
57
|
+
when Symbol then value
|
58
|
+
else raise
|
59
|
+
end
|
60
|
+
rescue StandardError => err
|
61
|
+
raise Fixturama::FixtureError.new("a messages chain", options, err)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Fixturama::Changes::Chain
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# Keep arguments of a message chain along with the corresponding actions
|
5
|
+
#
|
6
|
+
class Actions
|
7
|
+
def next
|
8
|
+
@list.count > 1 ? @list.pop : @list.first
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def initialize(*list)
|
14
|
+
list = [{ return: nil }] if list.empty?
|
15
|
+
|
16
|
+
@list = list.flatten.reverse.flat_map do |item|
|
17
|
+
action = build(item)
|
18
|
+
[action] * action.repeat
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def build(item)
|
23
|
+
item = Hash(item).transform_keys(&:to_sym)
|
24
|
+
case item.slice(:return, :raise).keys
|
25
|
+
when %i[return] then ReturnAction.new(item)
|
26
|
+
when %i[raise] then RaiseAction.new(item)
|
27
|
+
else raise Fixturama::FixtureError.new("an action", item)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Fixturama::Changes::Chain
|
2
|
+
#
|
3
|
+
# @private
|
4
|
+
# Keep a set of arguments along with the corresponding actions to be done
|
5
|
+
#
|
6
|
+
class Arguments
|
7
|
+
# @return [Array<Object>] the collection of arguments
|
8
|
+
attr_reader :arguments
|
9
|
+
|
10
|
+
# Order of comparing this set of arguments with the actual ones
|
11
|
+
# @return [Integer]
|
12
|
+
def order
|
13
|
+
-arguments.count
|
14
|
+
end
|
15
|
+
|
16
|
+
# Compare definitions by sets of arguments
|
17
|
+
# @param [Fixturama::Changes::Chain::Arguments] other
|
18
|
+
# @return [Boolean]
|
19
|
+
def ==(other)
|
20
|
+
other.arguments == arguments
|
21
|
+
end
|
22
|
+
|
23
|
+
# If actual arguments are covered by the current ones
|
24
|
+
def match?(*args, **opts)
|
25
|
+
return false if arguments.count > args.count + 1
|
26
|
+
|
27
|
+
arguments.first(args.count).zip(args).each do |(expected, actual)|
|
28
|
+
return false unless actual == expected
|
29
|
+
end
|
30
|
+
|
31
|
+
if arguments.count > args.count
|
32
|
+
Hash(arguments.last).transform_keys(&:to_sym).each do |k, v|
|
33
|
+
return false unless opts[k] == v
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Call the corresponding action if actual arguments are matched
|
41
|
+
def call
|
42
|
+
@actions.next.call
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def initialize(options)
|
48
|
+
@arguments = extract(options, :arguments)
|
49
|
+
@actions = Actions.new(*extract(options, :actions))
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract(options, key)
|
53
|
+
return [] unless options.key?(key)
|
54
|
+
|
55
|
+
source = options[key]
|
56
|
+
source.is_a?(Array) ? source : [source]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|