fixturama 0.1.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -0
  3. data/README.md +76 -4
  4. data/lib/fixturama.rb +48 -20
  5. data/lib/fixturama/changes.rb +72 -0
  6. data/lib/fixturama/changes/base.rb +28 -0
  7. data/lib/fixturama/changes/chain.rb +64 -0
  8. data/lib/fixturama/changes/chain/actions.rb +31 -0
  9. data/lib/fixturama/changes/chain/arguments.rb +59 -0
  10. data/lib/fixturama/changes/chain/raise_action.rb +43 -0
  11. data/lib/fixturama/changes/chain/return_action.rb +33 -0
  12. data/lib/fixturama/changes/const.rb +30 -0
  13. data/lib/fixturama/changes/env.rb +35 -0
  14. data/lib/fixturama/changes/request.rb +91 -0
  15. data/lib/fixturama/changes/request/response.rb +62 -0
  16. data/lib/fixturama/changes/request/responses.rb +22 -0
  17. data/lib/fixturama/changes/seed.rb +39 -0
  18. data/lib/fixturama/config.rb +5 -0
  19. data/lib/fixturama/fixture_error.rb +31 -0
  20. data/lib/fixturama/loader.rb +6 -2
  21. data/lib/fixturama/loader/context.rb +14 -5
  22. data/lib/fixturama/loader/value.rb +1 -0
  23. data/lib/fixturama/version.rb +4 -0
  24. metadata +85 -73
  25. data/.gitignore +0 -12
  26. data/.rspec +0 -2
  27. data/.rubocop.yml +0 -22
  28. data/.travis.yml +0 -17
  29. data/Gemfile +0 -9
  30. data/LICENSE.txt +0 -21
  31. data/Rakefile +0 -6
  32. data/fixturama.gemspec +0 -24
  33. data/lib/fixturama/seed.rb +0 -15
  34. data/lib/fixturama/stubs.rb +0 -79
  35. data/lib/fixturama/stubs/chain.rb +0 -90
  36. data/lib/fixturama/stubs/chain/actions.rb +0 -39
  37. data/lib/fixturama/stubs/chain/actions/raise.rb +0 -21
  38. data/lib/fixturama/stubs/chain/actions/return.rb +0 -23
  39. data/lib/fixturama/stubs/chain/arguments.rb +0 -86
  40. data/lib/fixturama/stubs/const.rb +0 -43
  41. data/lib/fixturama/stubs/request.rb +0 -98
  42. data/lib/fixturama/stubs/request/response.rb +0 -43
  43. data/lib/fixturama/stubs/request/responses.rb +0 -20
  44. data/lib/fixturama/utils.rb +0 -39
  45. data/spec/fixturama/load_fixture/_spec.rb +0 -53
  46. data/spec/fixturama/load_fixture/data.json +0 -5
  47. data/spec/fixturama/load_fixture/data.yaml +0 -3
  48. data/spec/fixturama/load_fixture/data.yml +0 -3
  49. data/spec/fixturama/seed_fixture/_spec.rb +0 -36
  50. data/spec/fixturama/seed_fixture/seed.yml +0 -16
  51. data/spec/fixturama/stub_fixture/_spec.rb +0 -120
  52. data/spec/fixturama/stub_fixture/stub.yml +0 -73
  53. data/spec/spec_helper.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bfb62ba99d427f8a3f03b6d46a0fb7e6712dd31bfff6da7c17fb075cfa930f0
4
- data.tar.gz: fa1b0359c0681a73723a915dd499945602b350b3b802b68e6962a66753a5d208
3
+ metadata.gz: 4e21218a92d62cab4b0bbc73fe1f2ac02eb5c66a7315357a68189067d138d92a
4
+ data.tar.gz: 81861ef1dbf1eba81874fb929875c89f04dc3ebdeba9694ae02989762dd7aca5
5
5
  SHA512:
6
- metadata.gz: 987b0cf303e78879b18278d85ace7cf26261e6c76000b0081571cb40a3dcf763435cb5560de122bf3a466b9c781684c8ed8d183697fbd1b84aa619c8409e34c8
7
- data.tar.gz: d85486458d90dbff1e3f2136c5862e081aa1a1861bb5e6320fb68bbc258d11ce1e978de5603c900080ef719809e97a4d0bf793a46c8f90af33e7a6626d258835
6
+ metadata.gz: f09063278691d752987cc1b9bbc8d20a715624cce81f81e9c61b521d52af28b9fbe03e09ac9357e01c154b987608558231b3c0ca8d1bdd5677303f887630b3ad
7
+ data.tar.gz: b93be44d444875df3d793bb7947bbd9c906a0fcc699c3b1ec4625186a38eed3b58b8a11f11d6e7ae421db4615534533029c2fece7d592a357649ef24598e63a9
data/CHANGELOG.md CHANGED
@@ -5,6 +5,100 @@ 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.5.0] - [2021-04-03]
9
+
10
+ ### Added
11
+
12
+ - Support for <de>serialization PORO objects (nepalez)
13
+
14
+ ```yaml
15
+ # target.yml
16
+ ---
17
+ number: <%= object(be_positive) %>
18
+ ```
19
+
20
+ ```ruby
21
+ RSpec.describe "something" do
22
+ subject { { "number" => 42 } }
23
+
24
+ # no explicit params is needed here
25
+ let(:target) { load_fixture "target.yml" }
26
+
27
+ it { is_expected.to match(target) }
28
+ end
29
+ ```
30
+
31
+ ## [0.4.1] - [2021-03-31]
32
+
33
+ ### Fixed
34
+
35
+ - Dependency from hashie updated to allow v4+ (nepalez)
36
+
37
+ ## [0.4.0] - [2020-03-15]
38
+
39
+ ### Added
40
+
41
+ - Support for stubbing ENV variables (nepalez)
42
+
43
+ ```yaml
44
+ ---
45
+ - env: GOOGLE_CLOUD_KEY
46
+ value: foo
47
+
48
+ - env: GOOGLE_CLOUD_PASSWORD
49
+ value: bar
50
+ ```
51
+
52
+ This would stub selected variables only, not touching the others
53
+
54
+ ## [0.3.0] - [2020-03-08]
55
+
56
+ ### Added
57
+
58
+ - Support for exception arguments (nepalez)
59
+
60
+ ```yaml
61
+ ---
62
+ - class: API
63
+ chain: get_product
64
+ arguments:
65
+ - 1
66
+ actions:
67
+ - raise: API::NotFoundError
68
+ arguments: # <--- that's that
69
+ - "Cannot find a product by id: 1"
70
+ ```
71
+
72
+ which would raise `API::NotFoundError.new("Cannot find a product by id: 1")`
73
+
74
+ ## [0.2.0] - [2020-02-17]
75
+
76
+ ### Added
77
+
78
+ - Stubbing and seeding from the same source file via the `call_fixture` method (nepalez)
79
+
80
+ ```yaml
81
+ # ./changes.yml
82
+ ---
83
+ - type: user
84
+ params:
85
+ id: 1
86
+
87
+ - const: DEFAULT_USER_ID
88
+ value: 1
89
+
90
+ - url: https://example.com/users/default
91
+ method: get
92
+ responses:
93
+ - body:
94
+ id: 1
95
+ name: Andrew
96
+ ```
97
+
98
+ ```ruby
99
+ before { call_fixture "#{__dir__}/changes.yml" }
100
+ ```
101
+
8
102
  ## [0.1.0] - [2020-02-09]
9
103
 
10
104
  ### Added
@@ -200,3 +294,8 @@ This is a first public release with features extracted from production app.
200
294
  [0.0.6]: https://github.com/nepalez/fixturama/compare/v0.0.5...v0.0.6
201
295
  [0.0.7]: https://github.com/nepalez/fixturama/compare/v0.0.6...v0.0.7
202
296
  [0.1.0]: https://github.com/nepalez/fixturama/compare/v0.0.7...v0.1.0
297
+ [0.2.0]: https://github.com/nepalez/fixturama/compare/v0.1.0...v0.2.0
298
+ [0.3.0]: https://github.com/nepalez/fixturama/compare/v0.2.0...v0.3.0
299
+ [0.4.0]: https://github.com/nepalez/fixturama/compare/v0.3.0...v0.4.0
300
+ [0.4.1]: https://github.com/nepalez/fixturama/compare/v0.4.0...v0.4.1
301
+ [0.5.0]: https://github.com/nepalez/fixturama/compare/v0.4.1...v0.5.0
data/README.md CHANGED
@@ -99,6 +99,38 @@ This feature can also be useful to produce a "partially defined" fixtures with [
99
99
  subject { load_fixture "#{__dir__}/data.yml", user: kind_of(ActiveRecord::Base) }
100
100
  ```
101
101
 
102
+ Since the v0.5.0 we support another way to serialize PORO objects in fixtures. Just wrap them to the `object()` method:
103
+
104
+ ```yaml
105
+ ---
106
+ :account: <%= object(user) %>
107
+ ```
108
+
109
+ This time you don't need sending objects explicitly.
110
+
111
+ ```ruby
112
+ RSpec.describe "example" do
113
+ subject { load_fixture "#{__dir__}/data.yml" }
114
+
115
+ let(:user) { FactoryBot.create(:user) }
116
+
117
+ # The same object will be returned
118
+ it { is_expected.to eq(account: user) }
119
+ end
120
+ ```
121
+
122
+ Under the hood we use `Marshal.dump` and `Marshal.restore` to serialize and deserialize the object back.
123
+
124
+ **Notice**, that deserialization creates a new instance of the object which is not equivalent to the source (`user` in the example above)!
125
+ In most cases this is enough. For example, you can provide matchers like:
126
+
127
+ ```yaml
128
+ ---
129
+ number: <%= object(be_positive) %>
130
+ ```
131
+
132
+ The loaded object would contain `{ "number" => be_positive }`.
133
+
102
134
  ### Seeding
103
135
 
104
136
  The seed (`seed_fixture`) file should be a YAML/JSON with opinionated parameters, namely:
@@ -124,22 +156,28 @@ Use the `count: 2` key to create more objects at once.
124
156
 
125
157
  ### Stubbing
126
158
 
127
- Another opinionated format we use for stubs (`stub_fixture`). The gem supports stubbing both message chains and constants.
159
+ The gem supports stubbing message chains, constants and http requests with the following keys.
128
160
 
129
161
  For message chains:
130
162
 
131
163
  - `class` for stubbed class
132
164
  - `chain` for messages chain
133
165
  - `arguments` (optional) for specific arguments
134
- - `actions` for an array of actions for consecutive invocations of the chain
135
-
136
- Every action either `return` some value, or `raise` some exception
166
+ - `actions` for an array of actions for consecutive invocations of the chain with keys
167
+ - `return` for a value to be returned
168
+ - `raise` for an exception to be risen
169
+ - `repeate` for a number of invocations with this action
137
170
 
138
171
  For constants:
139
172
 
140
173
  - `const` for stubbed constant
141
174
  - `value` for a value of the constant
142
175
 
176
+ For environment variables:
177
+
178
+ - `env` for the name of a variable
179
+ `value` for a value of the variable
180
+
143
181
  For http requests:
144
182
 
145
183
  - `url` or `uri` for the URI of the request (treats values like `/.../` as regular expressions)
@@ -152,6 +190,7 @@ For http requests:
152
190
  - `status`
153
191
  - `body`
154
192
  - `headers`
193
+ - `repeate` for the number of times this response should be returned before switching to the next one
155
194
 
156
195
  ```yaml
157
196
  # ./stubs.yml
@@ -179,11 +218,19 @@ For http requests:
179
218
  - <%= profile_id %>
180
219
  actions:
181
220
  - return: true
221
+ repeate: 1 # this is the default value
182
222
  - raise: ActiveRecord::RecordNotFound
223
+ arguments:
224
+ - "Profile with id: 1 not found" # for error message
183
225
 
226
+ # Here we stubbing a constant
184
227
  - const: NOTIFIER_TIMEOUT_SEC
185
228
  value: 10
186
229
 
230
+ # This is a stub for ENV['DEFAULT_EMAIL']
231
+ - env: DEFAULT_EMAIL
232
+ value: foo@example.com
233
+
187
234
  # Examples for stubbing HTTP
188
235
  - uri: /example.com/foo/ # regexp!
189
236
  method: delete
@@ -192,6 +239,7 @@ For http requests:
192
239
  password: bar
193
240
  responses:
194
241
  - status: 200 # for the first call
242
+ repeate: 1 # this is the default value, but you can set another one
195
243
  - status: 404 # for any other call
196
244
 
197
245
  - uri: htpps://example.com/foo # exact string!
@@ -231,6 +279,30 @@ I find it especially helpful when I need to check different edge cases. Instead
231
279
 
232
280
  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
281
 
282
+ ## Single Source of Changes
283
+
284
+ If you will, you can list all stubs and seeds at the one single file like
285
+
286
+ ```yaml
287
+ # ./changes.yml
288
+ ---
289
+ - type: user
290
+ params:
291
+ id: 1
292
+ name: Andrew
293
+
294
+ - const: DEFAULT_USER_ID
295
+ value: 1
296
+ ```
297
+
298
+ This fixture can be applied via `call_fixture` method just like we did above with `seed_fixture` and `stub_fixture`:
299
+
300
+ ```ruby
301
+ before { call_fixture "#{__dir__}/changes.yml" }
302
+ ```
303
+
304
+ In fact, since the `v0.2.0` all those methods are just the aliases of the `call_fixture`.
305
+
234
306
  ## License
235
307
 
236
308
  The gem is available as open source under the terms of the [MIT License][license].
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/stubs"
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
- def stub_fixture(path, **opts)
21
- items = load_fixture(path, **opts)
22
- raise "The fixture should contain an array" unless items.is_a?(Array)
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
- items.each { |item| fixturama_stubs.add(item) }
25
- fixturama_stubs.apply(self)
37
+ ERB.new(content).result(bindings)
26
38
  end
27
39
 
28
- def seed_fixture(path, **opts)
29
- Array(load_fixture(path, **opts)).each { |item| Seed.call(item) }
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(self, path, options).call
30
46
  end
31
47
 
32
- def load_fixture(path, **opts)
33
- Loader.new(path, opts).call
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
- def read_fixture(path, **opts)
37
- content = File.read(path)
38
- hashie = Hashie::Mash.new(opts)
39
- bindings = hashie.instance_eval { binding }
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
- ERB.new(content).result(bindings)
42
- end
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 fixturama_stubs
47
- @fixturama_stubs ||= Stubs.new
74
+ def changes
75
+ @changes ||= Changes.new
48
76
  end
49
77
  end
@@ -0,0 +1,72 @@
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/env"
11
+ require_relative "changes/request"
12
+ require_relative "changes/seed"
13
+
14
+ # Match option keys to the type of an item
15
+ TYPES = {
16
+ actions: Chain,
17
+ arguments: Chain,
18
+ basic_auth: Request,
19
+ body: Request,
20
+ chain: Chain,
21
+ class: Chain,
22
+ const: Const,
23
+ count: Seed,
24
+ env: Env,
25
+ headers: Request,
26
+ http_method: Request,
27
+ object: Chain,
28
+ params: Seed,
29
+ query: Request,
30
+ response: Request,
31
+ responses: Request,
32
+ traits: Seed,
33
+ type: Seed,
34
+ uri: Request,
35
+ url: Request,
36
+ }.freeze
37
+
38
+ # Adds new change to the registry
39
+ # @param [Hash] options
40
+ # @return [Fixturama::Changes]
41
+ # @raise [Fixturama::FixtureError] if the options cannot be processed
42
+ def add(options)
43
+ options = Hash(options).transform_keys(&:to_sym)
44
+ types = options.keys.map { |key| TYPES[key] }.compact.uniq
45
+ raise "Wrong count" unless types.count == 1
46
+
47
+ @changes << types.first.new(options)
48
+ self
49
+ rescue FixtureError => err
50
+ raise err
51
+ rescue StandardError => err
52
+ raise FixtureError.new("an operation", options, err)
53
+ end
54
+
55
+ # Apply all registered changes to the RSpec example
56
+ # @param [RSpec::Core::Example] example
57
+ # @return [self]
58
+ def call(example)
59
+ @changes
60
+ .group_by(&:key)
61
+ .values
62
+ .map { |changes| changes.reduce :merge }
63
+ .each { |change| change.call(example) }
64
+ end
65
+
66
+ private
67
+
68
+ def initialize
69
+ @changes = []
70
+ end
71
+ end
72
+ 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.instance_of?(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