fixturama 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|