fixtury 1.0.0.beta2 → 1.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +94 -8
- data/lib/fixtury/configuration.rb +34 -26
- data/lib/fixtury/{test_hooks.rb → minitest_hooks.rb} +4 -4
- data/lib/fixtury/mutation_observer.rb +0 -3
- data/lib/fixtury/railtie.rb +25 -4
- data/lib/fixtury/reference.rb +1 -1
- data/lib/fixtury/schema.rb +4 -0
- data/lib/fixtury/store.rb +15 -10
- data/lib/fixtury/version.rb +1 -1
- data/lib/fixtury.rb +11 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40d7b20a26b6cd75ff04db4f5208a5386dd941a3ee7b36bccf9533c0737ae159
|
4
|
+
data.tar.gz: b0adbfcdfb0b7b33e80e4d5e27d69c0660858492fbe6e2a06212c8c857d4210d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c931673f51c04fb39d486a33b9d0ff29040d32fe09907520ddf0d82a65db1fdaa7312b8e1841c99c927048b8650377b70736f098071e49a2cf4cacc5f8325a1
|
7
|
+
data.tar.gz: b6873bea9e6fbe6dd32e97a87d41073b6167087805d8646b78f10b4324c07baf229bd0735ac084e9c06476eb10bc9210ddf10bb8270293156b3911f61703337e
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# Fixtury
|
2
2
|
|
3
|
-
Fixtury aims to provide an interface for creating, managing, and accessing test data in a simple and
|
3
|
+
Fixtury aims to provide an interface for creating, managing, and accessing test data in a simple and on-demand way. It has no opinion on how you generate the data, it simply provides efficient ways to access it.
|
4
4
|
|
5
|
-
Often, fixture frameworks require you to either heavily maintain static fixtures or generate all your fixtures at runtime. Fixtury attempts to find a middle ground that enables a faster and more effecient development process.
|
5
|
+
Often, fixture frameworks require you to either heavily maintain static fixtures or generate all your fixtures at runtime. Fixtury attempts to find a middle ground that enables a faster and more effecient development process while allowing you to generate realistic test data.
|
6
6
|
|
7
7
|
For example, if a developer is running a test locally in their development environment there's no reason to build all fixtures for your suite of 30k tests. Instead, if we're able to track the fixture dependencies of the tests that are running we can build (and cache) the data relevant for the specific tests that are run.
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
|
11
|
-
prepend ::Fixtury::TestHooks
|
10
|
+
require "fixtury/minitest_hooks"
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
class MyTest < ::Minitest::Test
|
13
|
+
prepend ::Fixtury::MintestHooks
|
14
|
+
|
15
|
+
fixtury "users/fresh", as: :user
|
15
16
|
|
16
17
|
def test_whatever
|
17
18
|
assert_eq "Doug", user.first_name
|
@@ -20,6 +21,91 @@ class MyTest < ::ActiveSupport::TestCase
|
|
20
21
|
end
|
21
22
|
```
|
22
23
|
|
23
|
-
Loading this file would ensure `users/fresh` is loaded into the fixture set before the suite is run. In the context of
|
24
|
+
Loading this file would ensure `users/fresh` is loaded into the fixture set before the suite is run. In the context of Minitest::Test, the Fixtury::MinitestHooks file will ensure the database records are present prior to your suite running. If you're using ActiveRecord `use_transactional_fixtures` and `use_transactional_tests` will be respected.
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
If you're using Rails, you can `require "fixtury/railtie"` to accomplish a standard installation which will observe common rails files for changes and expects fixture definitions to defined in `test/fixtures`. See the railtie class for details.
|
29
|
+
|
30
|
+
For non-rails environments or additional configuration, you can open up the Fixtury configuration like so:
|
31
|
+
```ruby
|
32
|
+
::Fixtury.configure do |config|
|
33
|
+
config.locator_backend = :global_id # the locator behavior to use for finding fixtures
|
34
|
+
config.filepath = File.join(root, "tmp/fixtury.yml") # the location to dump the fixtury references
|
35
|
+
config.add_fixture_path = File.join(root, "fixtures/**/*.rb")
|
36
|
+
config.add_dependency_path = File.join(root, "db/schema.rb")
|
37
|
+
end
|
38
|
+
```
|
39
|
+
See Fixtury::Configuration for all options.
|
40
|
+
|
41
|
+
When your Fixtury is configured, you should call `Fixtury.start`.
|
42
|
+
|
43
|
+
For minitest integration, you should dump the configuration file after the suite runs or after your fixture dependencies are built:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
::Minitest.after_run do
|
47
|
+
::Fixtury.configuration.dump_file
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
In a CI environment, we'd likely want to preload all fixtures to produce a database snapshot to be shared. This can be done by configuring Fixtury, calling `Fixtury.start`, then calling `Fixtury.load_all_fixtures`. All fixtures declared in the configuration's fixture_paths will be loaded.
|
52
|
+
|
53
|
+
## Defining Fixtures
|
54
|
+
|
55
|
+
There are two primary principals in Fixtury: namespaces and fixture definitions. See below for an example of how they're used.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Fixtury.define do
|
59
|
+
|
60
|
+
fixture "user" do
|
61
|
+
User.create(...)
|
62
|
+
end
|
63
|
+
|
64
|
+
namespace "addresses" do
|
65
|
+
fixture "sample" do
|
66
|
+
Address.create(...)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
namespace "user_with_address" do
|
71
|
+
fixture "user", deps: "address" do |deps|
|
72
|
+
User.create(address_id: deps.address.id, ...)
|
73
|
+
end
|
74
|
+
|
75
|
+
fixture "address" do
|
76
|
+
Address.create(...)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
As you can see fixtures are named in a nested structure and can refer to each other via dependencies. See Fixtury::Dependency for more specifics.
|
83
|
+
|
84
|
+
## Isolation Levels
|
85
|
+
|
86
|
+
Isolation keys enable groups of fixtures to use and modify the same resources. When one fixture from an isolation level is built, all fixtures in that isolation level are built. This allows multiple fixtures to potentially mutate a resource while keeping the definition consistent.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Fixtury.define do
|
90
|
+
namespace "use_cases" do
|
91
|
+
namespace "onboarded", isolate: true do
|
92
|
+
|
93
|
+
fixture "user" do
|
94
|
+
User.create(...)
|
95
|
+
end
|
96
|
+
|
97
|
+
fixture "profile", deps: "user" do |deps|
|
98
|
+
profile = Profile.create(user: deps.user, ...)
|
99
|
+
user.update(profiles_count: 1, onboarded_at: Time.current)
|
100
|
+
profile
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
### ActiveRecord Integration
|
109
|
+
|
110
|
+
When installed with the railtie, a MutationObserver module is prepended into ActiveRecord::Base. It observes record mutations and ensures a record is not mutated outside of the declared isolation level. If you're not using ActiveRecord check out Fixtury::MutationObserver to see how you could hook into other frameworks.
|
24
111
|
|
25
|
-
In a CI environment, we'd likely want to preload all fixtures. This can be done by requiring all the test files, then telling the fixtury store to load all definitions.
|
@@ -5,12 +5,13 @@ module Fixtury
|
|
5
5
|
# generation.
|
6
6
|
class Configuration
|
7
7
|
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :fixture_files, :dependency_files, :locator_backend
|
9
|
+
attr_accessor :filepath, :reference_ttl
|
9
10
|
|
10
11
|
def initialize
|
11
|
-
@filepath = nil
|
12
12
|
@fixture_files = Set.new
|
13
13
|
@dependency_files = Set.new
|
14
|
+
@locator_backend = :memory
|
14
15
|
end
|
15
16
|
|
16
17
|
def log_level
|
@@ -22,19 +23,19 @@ module Fixtury
|
|
22
23
|
@log_level
|
23
24
|
end
|
24
25
|
|
26
|
+
def log_level=(level)
|
27
|
+
@log_level = level.to_s.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
def locator_backend=(backend)
|
31
|
+
@locator_backend = backend.to_sym
|
32
|
+
end
|
33
|
+
|
25
34
|
# Delete the storage file if it exists.
|
26
35
|
def reset
|
27
36
|
File.delete(filepath) if filepath && File.file?(filepath)
|
28
37
|
end
|
29
38
|
|
30
|
-
# Set the location of the storage file. The storage file will maintain
|
31
|
-
# checksums of all tracked files and serialized references to fixtures.
|
32
|
-
#
|
33
|
-
# @param path [String] The path to the storage file.
|
34
|
-
def filepath=(path)
|
35
|
-
@filepath = path.to_s
|
36
|
-
end
|
37
|
-
|
38
39
|
# Add a file or glob pattern to the list of fixture files.
|
39
40
|
#
|
40
41
|
# @param path_or_globs [String, Array<String>] The file or glob pattern(s) to add.
|
@@ -69,6 +70,29 @@ module Fixtury
|
|
69
70
|
File.binwrite(filepath, file_data.to_yaml)
|
70
71
|
end
|
71
72
|
|
73
|
+
def changes
|
74
|
+
return "new: #{filepath}" if stored_data.nil?
|
75
|
+
|
76
|
+
stored_checksums = (stored_data[:dependencies] || {})
|
77
|
+
seen_filepaths = []
|
78
|
+
calculate_checksums do |filepath, checksum|
|
79
|
+
# Early return if the checksums don't match
|
80
|
+
return "change: #{filepath}" unless stored_checksums[filepath] == checksum
|
81
|
+
|
82
|
+
seen_filepaths << filepath
|
83
|
+
end
|
84
|
+
|
85
|
+
# If we have a new file or a file has been removed, we need to report a change.
|
86
|
+
new_files = seen_filepaths - stored_checksums.keys
|
87
|
+
return "added: #{new_files.inspect}" if new_files.any?
|
88
|
+
|
89
|
+
removed_files = stored_checksums.keys - seen_filepaths
|
90
|
+
return "removed: #{removed_files.inspect}" if removed_files.any?
|
91
|
+
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
|
72
96
|
private
|
73
97
|
|
74
98
|
def file_data
|
@@ -90,22 +114,6 @@ module Fixtury
|
|
90
114
|
YAML.unsafe_load_file(filepath)
|
91
115
|
end
|
92
116
|
|
93
|
-
def files_changed?
|
94
|
-
return true if stored_data.nil?
|
95
|
-
|
96
|
-
stored_checksums = (stored_data[:dependencies] || {})
|
97
|
-
seen_filepaths = []
|
98
|
-
calculate_checksums do |filepath, checksum|
|
99
|
-
# Early return if the checksums don't match
|
100
|
-
return true unless stored_checksums[filepath] == checksum
|
101
|
-
|
102
|
-
seen_filepaths << filepath
|
103
|
-
end
|
104
|
-
|
105
|
-
# If we have a new file or a file has been removed, we need to report a change.
|
106
|
-
seen_filepaths.sort != stored_checksums.keys.sort
|
107
|
-
end
|
108
|
-
|
109
117
|
def calculate_checksums(&block)
|
110
118
|
(fixture_files.to_a | dependency_files.to_a).sort.each do |filepath|
|
111
119
|
yield filepath, Digest::MD5.file(filepath).hexdigest
|
@@ -4,7 +4,7 @@ require "fixtury"
|
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
5
|
|
6
6
|
module Fixtury
|
7
|
-
#
|
7
|
+
# MinitestHooks is a module designed to hook into a Minitest test case, and
|
8
8
|
# provide a way to load fixtures into the test case. It is designed to be
|
9
9
|
# prepended into the test case class, and will automatically load fixtures
|
10
10
|
# before the test case is setup, and rollback any changes after the test
|
@@ -15,7 +15,7 @@ module Fixtury
|
|
15
15
|
#
|
16
16
|
# @example
|
17
17
|
# class MyTest < Minitest::Test
|
18
|
-
# prepend Fixtury::
|
18
|
+
# prepend Fixtury::MinitestHooks
|
19
19
|
#
|
20
20
|
# fixtury "user"
|
21
21
|
# fixtury "post"
|
@@ -38,7 +38,7 @@ module Fixtury
|
|
38
38
|
# # using the last segment of the fixture's pathname.
|
39
39
|
#
|
40
40
|
# class MyTest < Minitest::Test
|
41
|
-
# prepend Fixtury::
|
41
|
+
# prepend Fixtury::MinitestHooks
|
42
42
|
#
|
43
43
|
# fixtury "/my/user_record", as: :user
|
44
44
|
#
|
@@ -52,7 +52,7 @@ module Fixtury
|
|
52
52
|
# The setup and teardown attempt to manage a transaction for each registered database
|
53
53
|
# connection if ActiveRecord::Base is present. If use_transaction_tests or use_transactional_fixtures
|
54
54
|
# are present, those settings will be respected. If neither are present, a transaction will be used.
|
55
|
-
module
|
55
|
+
module MinitestHooks
|
56
56
|
|
57
57
|
def self.prepended(klass)
|
58
58
|
klass.class_attribute :fixtury_dependencies
|
@@ -135,6 +135,3 @@ end
|
|
135
135
|
::Fixtury.hooks.around(:execution) do |execution, &block|
|
136
136
|
::Fixtury::MutationObserver.observe(execution, &block)
|
137
137
|
end
|
138
|
-
|
139
|
-
# If/when activerecord loads, prepend the hooks module to ActiveRecord::Base
|
140
|
-
ActiveSupport.on_load(:active_record) { prepend Fixtury::MutationObserver::ActiveRecordHooks }
|
data/lib/fixtury/railtie.rb
CHANGED
@@ -1,14 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "fixtury"
|
4
|
+
|
3
5
|
module Fixtury
|
4
6
|
class Railtie < ::Rails::Railtie
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
initializer "fixtury.configure" do
|
9
|
+
::Fixtury.configure do |config|
|
10
|
+
config.filepath = Rails.root.join("tmp/fixtury.yml")
|
11
|
+
config.add_dependency_path ::Rails.root.join("db/schema.rb")
|
12
|
+
config.add_dependency_path ::Rails.root.join("db/seeds.rb")
|
13
|
+
config.add_dependency_path ::Rails.root.join("db/seeds/**/*.rb")
|
14
|
+
config.add_fixture_path ::Rails.root.join("test/fixtures/**/*.rb")
|
15
|
+
config.locator_backend = :global_id
|
16
|
+
end
|
8
17
|
end
|
9
18
|
|
10
|
-
initializer "fixtury.
|
11
|
-
|
19
|
+
initializer "fixtury.load_hooks" do
|
20
|
+
ActiveSupport.on_load(:active_record) do
|
21
|
+
require "fixtury/mutation_observer"
|
22
|
+
prepend Fixtury::MutationObserver::ActiveRecordHooks
|
23
|
+
end
|
24
|
+
|
25
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
26
|
+
require "fixtury/minitest_hooks"
|
27
|
+
prepend Fixtury::MinitestHooks
|
28
|
+
|
29
|
+
::Minitest.after_run do
|
30
|
+
::Fixtury.configuration.dump_file
|
31
|
+
end
|
32
|
+
end
|
12
33
|
end
|
13
34
|
|
14
35
|
end
|
data/lib/fixtury/reference.rb
CHANGED
data/lib/fixtury/schema.rb
CHANGED
data/lib/fixtury/store.rb
CHANGED
@@ -14,15 +14,9 @@ module Fixtury
|
|
14
14
|
attr_reader :schema
|
15
15
|
attr_reader :ttl
|
16
16
|
|
17
|
-
|
18
|
-
# @param locator [Fixtury::Locator, Symbol, NilClass] (see Fixtury::Locator#from)
|
19
|
-
# @param ttl [Integer, NilClass] The time-to-live for references in seconds.
|
20
|
-
# @param schema [Fixtury::Schema, NilClass] The schema to use for fixture definitions, defaults to the global schema.
|
21
|
-
# @return [Fixtury::Store]
|
22
|
-
def initialize(locator: nil, ttl: nil, schema: nil)
|
17
|
+
def initialize(schema: nil)
|
23
18
|
@schema = schema || ::Fixtury.schema
|
24
|
-
@locator = ::Fixtury::Locator.from(
|
25
|
-
@ttl = ttl&.to_i
|
19
|
+
@locator = ::Fixtury::Locator.from(::Fixtury.configuration.locator_backend)
|
26
20
|
self.references = ::Fixtury.configuration.stored_references
|
27
21
|
end
|
28
22
|
|
@@ -126,9 +120,10 @@ module Fixtury
|
|
126
120
|
raise ArgumentError, "#{search.inspect} must refer to a definition" unless dfn.acts_like?(:fixtury_definition)
|
127
121
|
|
128
122
|
pathname = dfn.pathname
|
123
|
+
isokey = dfn.isolation_key
|
129
124
|
|
130
125
|
# Ensure that if we're part of an isolation group, we load all the fixtures in that group.
|
131
|
-
maybe_load_isolation_dependencies(
|
126
|
+
maybe_load_isolation_dependencies(isokey)
|
132
127
|
|
133
128
|
# See if we already hold a reference to the fixture.
|
134
129
|
ref = references[pathname]
|
@@ -173,7 +168,13 @@ module Fixtury
|
|
173
168
|
log("store #{pathname}", level: LOG_LEVEL_DEBUG)
|
174
169
|
|
175
170
|
locator_key = locator.dump(value, context: pathname)
|
176
|
-
|
171
|
+
reference_opts = {}
|
172
|
+
reference_opts[:isolation_key] = isokey unless isokey == pathname
|
173
|
+
references[pathname] = ::Fixtury::Reference.new(
|
174
|
+
pathname,
|
175
|
+
locator_key,
|
176
|
+
**reference_opts
|
177
|
+
)
|
177
178
|
end
|
178
179
|
|
179
180
|
value
|
@@ -243,5 +244,9 @@ module Fixtury
|
|
243
244
|
::Fixtury.log(msg, level: level, name: "store")
|
244
245
|
end
|
245
246
|
|
247
|
+
def ttl
|
248
|
+
::Fixtury.configuration.reference_ttl&.to_i
|
249
|
+
end
|
250
|
+
|
246
251
|
end
|
247
252
|
end
|
data/lib/fixtury/version.rb
CHANGED
data/lib/fixtury.rb
CHANGED
@@ -67,17 +67,16 @@ module Fixtury
|
|
67
67
|
@schema ||= ::Fixtury::Schema.new
|
68
68
|
end
|
69
69
|
|
70
|
-
def self.schema=(schema)
|
71
|
-
@schema = schema
|
72
|
-
end
|
73
|
-
|
74
70
|
# Default store for fixtures. This is a shared store that can be used across the application.
|
75
71
|
def self.store
|
76
72
|
@store ||= ::Fixtury::Store.new
|
77
73
|
end
|
78
74
|
|
79
|
-
|
80
|
-
|
75
|
+
# Load all known fixture files configured in Configuration. Reset the store references if
|
76
|
+
# a dependency file has changed.
|
77
|
+
def self.start
|
78
|
+
load_all_schemas
|
79
|
+
reset_if_changed
|
81
80
|
end
|
82
81
|
|
83
82
|
# Require each schema file to ensure that all definitions are loaded.
|
@@ -90,20 +89,20 @@ module Fixtury
|
|
90
89
|
# Ensure all definitions are loaded and then load all known fixtures.
|
91
90
|
def self.load_all_fixtures
|
92
91
|
load_all_schemas
|
93
|
-
|
92
|
+
store.load_all
|
94
93
|
end
|
95
94
|
|
96
|
-
# Remove all references from the active store and reset the dependency file
|
95
|
+
# Remove all references from the active store and reset the dependency file.
|
97
96
|
def self.reset
|
98
|
-
log("resetting", level: LOG_LEVEL_INFO)
|
99
|
-
|
100
97
|
configuration.reset
|
101
98
|
store.reset
|
102
99
|
end
|
103
100
|
|
104
101
|
# Perform a reset if any of the tracked files have changed.
|
105
102
|
def self.reset_if_changed
|
106
|
-
|
103
|
+
changes = configuration.changes
|
104
|
+
if changes
|
105
|
+
log("changes found, resetting | #{changes}", level: LOG_LEVEL_INFO)
|
107
106
|
reset
|
108
107
|
else
|
109
108
|
log("no changes, skipping reset", level: LOG_LEVEL_INFO)
|
@@ -124,6 +123,7 @@ module Fixtury
|
|
124
123
|
msg << " #{yield}" if block_given?
|
125
124
|
msg << "\n" if newline
|
126
125
|
|
126
|
+
# TODO: logger
|
127
127
|
print msg
|
128
128
|
msg
|
129
129
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fixtury
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
@@ -165,6 +165,7 @@ files:
|
|
165
165
|
- lib/fixtury/locator_backend/common.rb
|
166
166
|
- lib/fixtury/locator_backend/global_id.rb
|
167
167
|
- lib/fixtury/locator_backend/memory.rb
|
168
|
+
- lib/fixtury/minitest_hooks.rb
|
168
169
|
- lib/fixtury/mutation_observer.rb
|
169
170
|
- lib/fixtury/path_resolver.rb
|
170
171
|
- lib/fixtury/railtie.rb
|
@@ -172,7 +173,6 @@ files:
|
|
172
173
|
- lib/fixtury/schema.rb
|
173
174
|
- lib/fixtury/schema_node.rb
|
174
175
|
- lib/fixtury/store.rb
|
175
|
-
- lib/fixtury/test_hooks.rb
|
176
176
|
- lib/fixtury/version.rb
|
177
177
|
homepage: https://github.com/guideline-tech/fixtury
|
178
178
|
licenses: []
|