fixtury 1.0.0.beta3 → 1.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e87073aed4eaadac63331ef0ca8613b5ed5534ad2d29886bc8c7a2a34a828d52
4
- data.tar.gz: adf2bdb804f103bcafa7967bf47d7a405c9a05d1c2ac11f56826491c892133bd
3
+ metadata.gz: 297aa8d54ad35f629cfb75dc8c7599660dc24393ad87b03de039ad8f4dfd691a
4
+ data.tar.gz: c89efb954c5bebe6751628d22b29ecc4fb1fd61020d53d321b7e782e93608007
5
5
  SHA512:
6
- metadata.gz: c6e0314bbc8fbb13465d8cb1a27f7b431f1c9ba788a218bc9754926a2c18390d6ebbd95897e547cc675e002704954e6616cec3dc0375def04852f0a49070a4ea
7
- data.tar.gz: d4cadd16d4aad50d5ec31d56eb5a66a8e2d3afc14d22d1bec298768bcf9af342bb13db0126a48d9b61e4fd19e1691f3d647b6213105e9f3f4b49791db4db5234
6
+ metadata.gz: 9af1d90c107d075bfdf89c50ed59b59b0ae264a7799058bab495ae66f0e0451876c90d119e24219880d193f6dc6a869798cb7f568885a5b85e5f676f9871221d
7
+ data.tar.gz: de852c51ecd2a53fa9ae050deea5d150fef75819c287330c831c99c9307b00ea98854e6c13df3dc9b03e5dd5119bf6805cee69a139199c22a0867b0a848d25ad
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fixtury (1.0.0.beta3)
4
+ fixtury (1.0.0.beta5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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 efficient way. It has no opinion on how you generate the data, it simply provides efficient ways to access it.
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
- class MyTest < ::ActiveSupport::TestCase
11
- prepend ::Fixtury::TestHooks
10
+ require "fixtury/minitest_hooks"
12
11
 
13
- fixtury "users/fresh"
14
- let(:user) { fixtury("users/fresh") }
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 ActiveSupport::TestCase, the Fixtury::Hooks file will ensure the database records are present prior to your suite running. Setting `use_transactional_fixtures` ensures all records are rolled back prior to running another test.
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,14 @@ module Fixtury
5
5
  # generation.
6
6
  class Configuration
7
7
 
8
- attr_reader :filepath, :fixture_files, :dependency_files
8
+ attr_reader :fixture_files, :dependency_files, :locator_backend
9
+ attr_accessor :filepath, :reference_ttl, :strict_dependencies
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
15
+ @strict_dependencies = true
14
16
  end
15
17
 
16
18
  def log_level
@@ -22,19 +24,19 @@ module Fixtury
22
24
  @log_level
23
25
  end
24
26
 
27
+ def log_level=(level)
28
+ @log_level = level.to_s.to_sym
29
+ end
30
+
31
+ def locator_backend=(backend)
32
+ @locator_backend = backend.to_sym
33
+ end
34
+
25
35
  # Delete the storage file if it exists.
26
36
  def reset
27
37
  File.delete(filepath) if filepath && File.file?(filepath)
28
38
  end
29
39
 
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
40
  # Add a file or glob pattern to the list of fixture files.
39
41
  #
40
42
  # @param path_or_globs [String, Array<String>] The file or glob pattern(s) to add.
@@ -69,20 +71,26 @@ module Fixtury
69
71
  File.binwrite(filepath, file_data.to_yaml)
70
72
  end
71
73
 
72
- def files_changed?
73
- return true if stored_data.nil?
74
+ def changes
75
+ return "new: #{filepath}" if stored_data.nil?
74
76
 
75
77
  stored_checksums = (stored_data[:dependencies] || {})
76
78
  seen_filepaths = []
77
79
  calculate_checksums do |filepath, checksum|
78
80
  # Early return if the checksums don't match
79
- return true unless stored_checksums[filepath] == checksum
81
+ return "change: #{filepath}" unless stored_checksums[filepath] == checksum
80
82
 
81
83
  seen_filepaths << filepath
82
84
  end
83
85
 
84
86
  # If we have a new file or a file has been removed, we need to report a change.
85
- seen_filepaths.sort != stored_checksums.keys.sort
87
+ new_files = seen_filepaths - stored_checksums.keys
88
+ return "added: #{new_files.inspect}" if new_files.any?
89
+
90
+ removed_files = stored_checksums.keys - seen_filepaths
91
+ return "removed: #{removed_files.inspect}" if removed_files.any?
92
+
93
+ nil
86
94
  end
87
95
 
88
96
 
@@ -14,16 +14,27 @@ module Fixtury
14
14
  "#{self.class}(definition: #{definition.pathname.inspect}, dependencies: #{definition.dependencies.keys.inspect})"
15
15
  end
16
16
 
17
- # Returns the value of the dependency with the given key
17
+ # Returns the value of the dependency with the given key. If the key is not present in the dependencies
18
+ # and strict_dependencies is enabled, an error will be raised. If strict_dependencies is not enabled
19
+ # the store will receive the search term directly.
18
20
  #
19
- # @param key [String, Symbol] the accessor of the dependency
21
+ # @param search [String, Symbol] the accessor of the dependency
20
22
  # @return [Object] the value of the dependency
21
- # @raise [Fixtury::Errors::UnknownDependencyError] if the definition does not contain the provided dependency
22
- def get(key)
23
- dep = definition.dependencies.fetch(key.to_s) do
24
- raise Errors::UnknownDependencyError.new(definition, key)
23
+ # @raise [Fixtury::Errors::UnknownDependencyError] if the definition does not contain the provided dependency and strict_dependencies is enabled
24
+ def get(search)
25
+ dep = definition.dependencies[search.to_s]
26
+
27
+ if dep.nil? && Fixtury.configuration.strict_dependencies
28
+ raise Errors::UnknownDependencyError.new(definition, search)
29
+ end
30
+
31
+ if dep
32
+ store.get(dep.definition.pathname)
33
+ else
34
+ store.with_relative_schema(definition.parent) do
35
+ store.get(search)
36
+ end
25
37
  end
26
- store.get(dep.definition.pathname)
27
38
  end
28
39
  alias [] get
29
40
 
@@ -4,7 +4,7 @@ require "fixtury"
4
4
  require "active_support/core_ext/class/attribute"
5
5
 
6
6
  module Fixtury
7
- # TestHooks is a module designed to hook into a Minitest test case, and
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::TestHooks
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::TestHooks
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 TestHooks
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 }
@@ -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
- rake_tasks do
7
- load "fixtury/tasks.rake"
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.activerecord_hooks" do
11
- require "fixtury/mutation_observer"
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
@@ -17,7 +17,7 @@ module Fixtury
17
17
 
18
18
  attr_reader :name, :locator_key, :created_at, :options
19
19
 
20
- def initialize(name, locator_key, options = {})
20
+ def initialize(name, locator_key, **options)
21
21
  @name = name
22
22
  @locator_key = locator_key
23
23
  @created_at = Time.now.to_i
@@ -13,6 +13,10 @@ module Fixtury
13
13
  super(name: name, **options)
14
14
  end
15
15
 
16
+ def reset
17
+ children.clear
18
+ end
19
+
16
20
  # Object#acts_like? adherence.
17
21
  def acts_like_fixtury_schema?
18
22
  true
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
- # Create a new store.
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(locator)
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
 
@@ -78,7 +72,7 @@ module Fixtury
78
72
  # @return [void]
79
73
  def load_all(schema = self.schema)
80
74
  schema.children.each_value do |item|
81
- get(item.name) if item.acts_like?(:fixtury_definition)
75
+ get(item.pathname) if item.acts_like?(:fixtury_definition)
82
76
  load_all(item) if item.acts_like?(:fixtury_schema)
83
77
  end
84
78
  end
@@ -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(dfn.isolation_key)
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
- references[pathname] = ::Fixtury::Reference.new(pathname, locator_key)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fixtury
4
4
 
5
- VERSION = "1.0.0.beta3"
5
+ VERSION = "1.0.0.beta5"
6
6
 
7
7
  end
data/lib/fixtury.rb CHANGED
@@ -67,43 +67,48 @@ 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
- def self.store=(store)
80
- @store = store
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.
84
- def self.load_all_schemas
83
+ def self.load_all_schemas(mechanism = :require)
85
84
  configuration.fixture_files.each do |filepath|
86
- require filepath
85
+ if mechanism == :require
86
+ require filepath
87
+ elsif mechanism == :load
88
+ load filepath
89
+ else
90
+ raise ArgumentError, "unknown load mechanism: #{mechanism}"
91
+ end
87
92
  end
88
93
  end
89
94
 
90
95
  # Ensure all definitions are loaded and then load all known fixtures.
91
- def self.load_all_fixtures
92
- load_all_schemas
93
- Fixtury.store.load_all
96
+ def self.load_all_fixtures(...)
97
+ load_all_schemas(...)
98
+ store.load_all
94
99
  end
95
100
 
96
- # Remove all references from the active store and reset the dependency file
101
+ # Remove all references from the active store and reset the dependency file.
97
102
  def self.reset
98
- log("resetting", level: LOG_LEVEL_INFO)
99
-
100
103
  configuration.reset
101
104
  store.reset
102
105
  end
103
106
 
104
107
  # Perform a reset if any of the tracked files have changed.
105
108
  def self.reset_if_changed
106
- if configuration.files_changed?
109
+ changes = configuration.changes
110
+ if changes
111
+ log("changes found, resetting | #{changes}", level: LOG_LEVEL_INFO)
107
112
  reset
108
113
  else
109
114
  log("no changes, skipping reset", level: LOG_LEVEL_INFO)
@@ -124,6 +129,7 @@ module Fixtury
124
129
  msg << " #{yield}" if block_given?
125
130
  msg << "\n" if newline
126
131
 
132
+ # TODO: logger
127
133
  print msg
128
134
  msg
129
135
  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.beta3
4
+ version: 1.0.0.beta5
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: []