fixture_kit 0.4.0 → 0.5.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 +46 -8
- data/lib/fixture_kit/cache.rb +20 -7
- data/lib/fixture_kit/configuration.rb +4 -2
- data/lib/fixture_kit/definition.rb +2 -1
- data/lib/fixture_kit/fixture.rb +8 -17
- data/lib/fixture_kit/minitest.rb +2 -2
- data/lib/fixture_kit/registry.rb +35 -14
- data/lib/fixture_kit/rspec.rb +2 -2
- data/lib/fixture_kit/runner.rb +13 -2
- data/lib/fixture_kit/version.rb +1 -1
- data/lib/fixture_kit.rb +2 -0
- 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: c052323f7423cfeb258b137bc8372833eff2f4c8b826b3fa22b25a171b2b2c0f
|
|
4
|
+
data.tar.gz: '0728daee8814033465d2b93a67e15a00eff4abe5543967167077f81e68db7dde'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82149fa2c3598b2af00b2cd9b8e3691e8694f18bdc40a0abf3a47540e7c319e0821250bec0e3dce4d8aaf692ea614a17cda09970a476503b0280c7a301b95e2f
|
|
7
|
+
data.tar.gz: f4fe1ea329c354aee57ad6f53d22682309b8db9704340f52d18c4b01b42a83a47701d36fe11e739d644e13b72d02171190a530df6847386770a5edc6b786cbf2
|
data/README.md
CHANGED
|
@@ -106,6 +106,22 @@ end
|
|
|
106
106
|
|
|
107
107
|
`fixture` returns a `Repository` and exposes records as methods (for example, `fixture.owner`).
|
|
108
108
|
|
|
109
|
+
You can also define fixtures anonymously inline:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
RSpec.describe Book do
|
|
113
|
+
fixture do
|
|
114
|
+
owner = User.create!(name: "Alice", email: "alice@example.com")
|
|
115
|
+
featured = Book.create!(title: "Dune", owner: owner)
|
|
116
|
+
expose(owner: owner, featured: featured)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "uses inline fixture data" do
|
|
120
|
+
expect(fixture.featured.owner).to eq(fixture.owner)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
109
125
|
### 3. Configure RSpec
|
|
110
126
|
|
|
111
127
|
```ruby
|
|
@@ -117,7 +133,7 @@ RSpec.configure do |config|
|
|
|
117
133
|
end
|
|
118
134
|
```
|
|
119
135
|
|
|
120
|
-
When you call `fixture "name"` in an example group, FixtureKit registers that fixture with its runner.
|
|
136
|
+
When you call `fixture "name"` or `fixture do ... end` in an example group, FixtureKit registers that fixture with its runner.
|
|
121
137
|
|
|
122
138
|
### 4. Configure Minitest
|
|
123
139
|
|
|
@@ -130,7 +146,7 @@ class ActiveSupport::TestCase
|
|
|
130
146
|
end
|
|
131
147
|
```
|
|
132
148
|
|
|
133
|
-
When you call `fixture "name"` in a test class, FixtureKit registers that fixture with its runner and mounts it during test setup.
|
|
149
|
+
When you call `fixture "name"` or `fixture do ... end` in a test class, FixtureKit registers that fixture with its runner and mounts it during test setup.
|
|
134
150
|
|
|
135
151
|
## Configuration
|
|
136
152
|
|
|
@@ -152,8 +168,14 @@ FixtureKit.configure do |config|
|
|
|
152
168
|
|
|
153
169
|
# Optional callback, called right before a fixture cache is generated.
|
|
154
170
|
# Called on first generation and forced regeneration.
|
|
155
|
-
# Receives the fixture
|
|
156
|
-
#
|
|
171
|
+
# Receives the fixture identifier:
|
|
172
|
+
# - named fixtures: String (e.g. "teams/basic")
|
|
173
|
+
# - anonymous fixtures: scope class
|
|
174
|
+
# config.on_cache_save = ->(identifier) { puts "cached #{identifier}" }
|
|
175
|
+
|
|
176
|
+
# Optional callback, called right before a fixture cache is mounted.
|
|
177
|
+
# Receives the same fixture identifier shape as on_cache_save.
|
|
178
|
+
# config.on_cache_mount = ->(identifier) { puts "mounted #{identifier}" }
|
|
157
179
|
end
|
|
158
180
|
```
|
|
159
181
|
|
|
@@ -168,7 +190,7 @@ When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpecIsolator`. It
|
|
|
168
190
|
|
|
169
191
|
Fixture generation is managed by `FixtureKit::Runner`.
|
|
170
192
|
|
|
171
|
-
1. Calling `fixture "name"` registers the fixture with the runner.
|
|
193
|
+
1. Calling `fixture "name"` or `fixture do ... end` registers the fixture with the runner.
|
|
172
194
|
2. Runner `start`:
|
|
173
195
|
- clears `cache_path` (unless preserve-cache is enabled),
|
|
174
196
|
- generates caches for all already-registered fixtures.
|
|
@@ -180,6 +202,13 @@ When runner start happens:
|
|
|
180
202
|
- `fixture_kit/rspec`: in `before(:suite)`.
|
|
181
203
|
- `fixture_kit/minitest`: lazily during test setup for the first test class that declares `fixture`.
|
|
182
204
|
|
|
205
|
+
## Fixture Declaration Rules
|
|
206
|
+
|
|
207
|
+
- Only one `fixture` declaration is allowed per test context.
|
|
208
|
+
- Declaring a fixture twice in the same context raises `FixtureKit::MultipleFixtures`.
|
|
209
|
+
- Child contexts/classes can declare their own fixture and override parent declarations.
|
|
210
|
+
- Providing both a name and a block (or neither) raises `FixtureKit::InvalidFixtureDeclaration`.
|
|
211
|
+
|
|
183
212
|
### Preserving Cache Locally
|
|
184
213
|
|
|
185
214
|
If you want to skip cache clearing when the runner starts (e.g., to reuse caches across test runs during local development), set the `FIXTURE_KIT_PRESERVE_CACHE` environment variable:
|
|
@@ -207,9 +236,18 @@ end
|
|
|
207
236
|
fixture "teams/sales"
|
|
208
237
|
```
|
|
209
238
|
|
|
239
|
+
## Anonymous Fixture Cache Paths
|
|
240
|
+
|
|
241
|
+
Anonymous fixture caches are written under the `_anonymous/` directory inside `cache_path`.
|
|
242
|
+
|
|
243
|
+
- Minitest: class name is underscored into a path.
|
|
244
|
+
- `MyFeatureTest` -> `_anonymous/my_feature_test.json`
|
|
245
|
+
- RSpec: class name is underscored after removing `RSpec::ExampleGroups::`.
|
|
246
|
+
- `RSpec::ExampleGroups::Foo::WithFixtureKit::Hello` -> `_anonymous/foo/with_fixture_kit/hello.json`
|
|
247
|
+
|
|
210
248
|
## How It Works
|
|
211
249
|
|
|
212
|
-
1. **Cache generation**: FixtureKit executes your definition block inside the configured isolator, subscribes to `sql.active_record` notifications to track created/updated/deleted models, queries those model tables, and caches
|
|
250
|
+
1. **Cache generation**: FixtureKit executes your definition block inside the configured isolator, subscribes to `sql.active_record` notifications to track created/updated/deleted models, queries those model tables, and caches SQL statements for current table contents.
|
|
213
251
|
|
|
214
252
|
2. **Mounting**: FixtureKit loads the cached JSON file, clears each tracked table, and executes the raw SQL INSERT statements directly. No ORM instantiation, no callbacks.
|
|
215
253
|
|
|
@@ -224,8 +262,8 @@ Caches are stored as JSON files in `tmp/cache/fixture_kit/`:
|
|
|
224
262
|
```json
|
|
225
263
|
{
|
|
226
264
|
"records": {
|
|
227
|
-
"User": "INSERT
|
|
228
|
-
"Project": "INSERT
|
|
265
|
+
"User": "INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com'), (2, 'Bob', 'bob@example.com')",
|
|
266
|
+
"Project": "INSERT INTO projects (id, name, user_id) VALUES (1, 'Website', 1)"
|
|
229
267
|
},
|
|
230
268
|
"exposed": {
|
|
231
269
|
"alice": { "model": "User", "id": 1 },
|
data/lib/fixture_kit/cache.rb
CHANGED
|
@@ -7,17 +7,30 @@ require "active_support/inflector"
|
|
|
7
7
|
|
|
8
8
|
module FixtureKit
|
|
9
9
|
class Cache
|
|
10
|
+
ANONYMOUS_DIRECTORY = "_anonymous"
|
|
11
|
+
|
|
10
12
|
include ConfigurationHelper
|
|
11
13
|
|
|
12
14
|
attr_reader :fixture
|
|
13
15
|
|
|
14
|
-
def initialize(fixture
|
|
16
|
+
def initialize(fixture)
|
|
15
17
|
@fixture = fixture
|
|
16
|
-
@definition = definition
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def path
|
|
20
|
-
File.join(configuration.cache_path, "#{
|
|
21
|
+
File.join(configuration.cache_path, "#{identifier}.json")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def identifier
|
|
25
|
+
@identifier ||= begin
|
|
26
|
+
raw_identifier = fixture.identifier
|
|
27
|
+
if raw_identifier.is_a?(String)
|
|
28
|
+
raw_identifier
|
|
29
|
+
else
|
|
30
|
+
normalized_scope = raw_identifier.to_s.sub(/\ARSpec::ExampleGroups::/, "")
|
|
31
|
+
File.join(ANONYMOUS_DIRECTORY, ActiveSupport::Inflector.underscore(normalized_scope))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
21
34
|
end
|
|
22
35
|
|
|
23
36
|
def exists?
|
|
@@ -26,7 +39,7 @@ module FixtureKit
|
|
|
26
39
|
|
|
27
40
|
def load
|
|
28
41
|
unless exists?
|
|
29
|
-
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{fixture.
|
|
42
|
+
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{fixture.identifier}'"
|
|
30
43
|
end
|
|
31
44
|
|
|
32
45
|
@data ||= JSON.parse(File.read(path))
|
|
@@ -46,12 +59,12 @@ module FixtureKit
|
|
|
46
59
|
def save
|
|
47
60
|
FixtureKit.runner.isolator.run do
|
|
48
61
|
models = SqlSubscriber.capture do
|
|
49
|
-
|
|
62
|
+
fixture.definition.evaluate
|
|
50
63
|
end
|
|
51
64
|
|
|
52
65
|
@data = {
|
|
53
66
|
"records" => generate_statements(models),
|
|
54
|
-
"exposed" => build_exposed_mapping(
|
|
67
|
+
"exposed" => build_exposed_mapping(fixture.definition.exposed)
|
|
55
68
|
}
|
|
56
69
|
end
|
|
57
70
|
|
|
@@ -77,7 +90,7 @@ module FixtureKit
|
|
|
77
90
|
model.find(id)
|
|
78
91
|
rescue ActiveRecord::RecordNotFound
|
|
79
92
|
raise FixtureKit::ExposedRecordNotFound,
|
|
80
|
-
"Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture.
|
|
93
|
+
"Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture.identifier}'"
|
|
81
94
|
end
|
|
82
95
|
|
|
83
96
|
def generate_statements(models)
|
|
@@ -5,13 +5,15 @@ module FixtureKit
|
|
|
5
5
|
attr_writer :fixture_path
|
|
6
6
|
attr_writer :cache_path
|
|
7
7
|
attr_accessor :isolator
|
|
8
|
-
attr_accessor :
|
|
8
|
+
attr_accessor :on_cache_save
|
|
9
|
+
attr_accessor :on_cache_mount
|
|
9
10
|
|
|
10
11
|
def initialize
|
|
11
12
|
@fixture_path = "fixture_kit"
|
|
12
13
|
@cache_path = nil
|
|
13
14
|
@isolator = FixtureKit::MinitestIsolator
|
|
14
|
-
@
|
|
15
|
+
@on_cache_save = nil
|
|
16
|
+
@on_cache_mount = nil
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def fixture_path
|
data/lib/fixture_kit/fixture.rb
CHANGED
|
@@ -4,37 +4,28 @@ module FixtureKit
|
|
|
4
4
|
class Fixture
|
|
5
5
|
include ConfigurationHelper
|
|
6
6
|
|
|
7
|
-
attr_reader :
|
|
7
|
+
attr_reader :identifier, :definition
|
|
8
8
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
11
|
-
@
|
|
12
|
-
@cache = Cache.new(self
|
|
9
|
+
def initialize(identifier, definition)
|
|
10
|
+
@identifier = identifier
|
|
11
|
+
@definition = definition
|
|
12
|
+
@cache = Cache.new(self)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def cache(force: false)
|
|
16
16
|
return if @cache.exists? && !force
|
|
17
17
|
|
|
18
|
-
configuration.
|
|
18
|
+
configuration.on_cache_save&.call(@cache.identifier)
|
|
19
19
|
@cache.save
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def mount
|
|
23
23
|
unless @cache.exists?
|
|
24
|
-
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{
|
|
24
|
+
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{identifier}'"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
configuration.on_cache_mount&.call(@cache.identifier)
|
|
27
28
|
@cache.load
|
|
28
29
|
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def definition
|
|
33
|
-
@definition ||= begin
|
|
34
|
-
definition = eval(File.read(@path), TOPLEVEL_BINDING.dup, @path)
|
|
35
|
-
raise FixtureKit::FixtureDefinitionNotFound, "Could not find fixture definition at '#{@path}'" unless definition.is_a?(Definition)
|
|
36
|
-
definition
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
30
|
end
|
|
40
31
|
end
|
data/lib/fixture_kit/minitest.rb
CHANGED
|
@@ -8,8 +8,8 @@ module FixtureKit
|
|
|
8
8
|
DECLARATION_CLASS_ATTRIBUTE = :fixture_kit_declaration
|
|
9
9
|
|
|
10
10
|
module ClassMethods
|
|
11
|
-
def fixture(name)
|
|
12
|
-
self.fixture_kit_declaration = FixtureKit.runner.register(name)
|
|
11
|
+
def fixture(name = nil, &definition_block)
|
|
12
|
+
self.fixture_kit_declaration = FixtureKit.runner.register(self, name, &definition_block)
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
data/lib/fixture_kit/registry.rb
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "pathname"
|
|
4
|
-
|
|
5
3
|
module FixtureKit
|
|
6
4
|
class Registry
|
|
7
5
|
include ConfigurationHelper
|
|
8
6
|
|
|
9
7
|
def initialize
|
|
10
|
-
@
|
|
8
|
+
@declarations = {}
|
|
9
|
+
@fixtures = {}
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
def add(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
file_path = fixture_file_path(name)
|
|
17
|
-
unless File.file?(file_path)
|
|
18
|
-
raise FixtureKit::FixtureDefinitionNotFound,
|
|
19
|
-
"Could not find fixture definition file for '#{name}' at '#{file_path}'"
|
|
12
|
+
def add(scope, name_or_block)
|
|
13
|
+
if @declarations.key?(scope)
|
|
14
|
+
raise FixtureKit::MultipleFixtures, "cannot load multiple fixtures in the same context"
|
|
20
15
|
end
|
|
21
16
|
|
|
22
|
-
@
|
|
17
|
+
@declarations[scope] =
|
|
18
|
+
case name_or_block
|
|
19
|
+
when String
|
|
20
|
+
fetch_named_fixture(name_or_block)
|
|
21
|
+
when Proc
|
|
22
|
+
fetch_anonymous_fixture(scope, name_or_block)
|
|
23
|
+
else
|
|
24
|
+
raise FixtureKit::InvalidFixtureDeclaration, "unsupported fixture declaration type: #{name_or_block.class}"
|
|
25
|
+
end
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def fixtures
|
|
26
|
-
@
|
|
29
|
+
@fixtures.values
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
private
|
|
30
33
|
|
|
31
|
-
def
|
|
32
|
-
|
|
34
|
+
def fetch_named_fixture(name)
|
|
35
|
+
@fixtures[name] ||= Fixture.new(name, load_named_definition(name))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fetch_anonymous_fixture(scope, definition)
|
|
39
|
+
@fixtures[scope] ||= Fixture.new(scope, Definition.new(&definition))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_named_definition(name)
|
|
43
|
+
file_path = File.expand_path(File.join(configuration.fixture_path, "#{name}.rb"))
|
|
44
|
+
|
|
45
|
+
unless File.file?(file_path)
|
|
46
|
+
raise FixtureKit::FixtureDefinitionNotFound,
|
|
47
|
+
"cannot find fixture definition file for '#{name}' at '#{file_path}'"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
definition = eval(File.read(file_path), TOPLEVEL_BINDING.dup, file_path)
|
|
51
|
+
return definition if definition.is_a?(Definition)
|
|
52
|
+
|
|
53
|
+
raise FixtureKit::FixtureDefinitionNotFound, "cannot find fixture definition at '#{file_path}'"
|
|
33
54
|
end
|
|
34
55
|
end
|
|
35
56
|
end
|
data/lib/fixture_kit/rspec.rb
CHANGED
|
@@ -27,8 +27,8 @@ module FixtureKit
|
|
|
27
27
|
# end
|
|
28
28
|
# end
|
|
29
29
|
# end
|
|
30
|
-
def fixture(name)
|
|
31
|
-
metadata[DECLARATION_METADATA_KEY] = ::RSpec.configuration.fixture_kit.register(name)
|
|
30
|
+
def fixture(name = nil, &definition_block)
|
|
31
|
+
metadata[DECLARATION_METADATA_KEY] = ::RSpec.configuration.fixture_kit.register(self, name, &definition_block)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
data/lib/fixture_kit/runner.rb
CHANGED
|
@@ -15,8 +15,8 @@ module FixtureKit
|
|
|
15
15
|
@started = false
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def register(name)
|
|
19
|
-
registry.add(name).tap do |fixture|
|
|
18
|
+
def register(scope, name = nil, &definition_block)
|
|
19
|
+
registry.add(scope, normalize_registration(name, definition_block)).tap do |fixture|
|
|
20
20
|
fixture.cache if started?
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -46,5 +46,16 @@ module FixtureKit
|
|
|
46
46
|
def preserve_cache?
|
|
47
47
|
ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
def normalize_registration(name, definition_block)
|
|
51
|
+
if name && definition_block
|
|
52
|
+
raise FixtureKit::InvalidFixtureDeclaration, "cannot provide both fixture name and definition block"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
name_or_block = name || definition_block
|
|
56
|
+
return name_or_block if name_or_block
|
|
57
|
+
|
|
58
|
+
raise FixtureKit::InvalidFixtureDeclaration, "must provide fixture name or definition block"
|
|
59
|
+
end
|
|
49
60
|
end
|
|
50
61
|
end
|
data/lib/fixture_kit/version.rb
CHANGED
data/lib/fixture_kit.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module FixtureKit
|
|
4
4
|
class Error < StandardError; end
|
|
5
5
|
class DuplicateNameError < Error; end
|
|
6
|
+
class InvalidFixtureDeclaration < Error; end
|
|
7
|
+
class MultipleFixtures < Error; end
|
|
6
8
|
class CacheMissingError < Error; end
|
|
7
9
|
class FixtureDefinitionNotFound < Error; end
|
|
8
10
|
class ExposedRecordNotFound < Error; end
|