test_data 0.0.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +41 -0
  3. data/.standard.yml +2 -0
  4. data/CHANGELOG.md +43 -0
  5. data/Gemfile.lock +17 -15
  6. data/LICENSE.txt +1 -6
  7. data/README.md +1232 -17
  8. data/example/.gitignore +1 -4
  9. data/example/Gemfile +3 -0
  10. data/example/Gemfile.lock +100 -71
  11. data/example/README.md +2 -22
  12. data/example/config/application.rb +3 -0
  13. data/example/config/credentials.yml.enc +1 -1
  14. data/example/config/database.yml +2 -0
  15. data/example/spec/rails_helper.rb +64 -0
  16. data/example/spec/requests/boops_spec.rb +17 -0
  17. data/example/spec/requests/rails_fixtures_override_spec.rb +106 -0
  18. data/example/spec/spec_helper.rb +94 -0
  19. data/example/test/factories.rb +4 -0
  20. data/example/test/integration/better_mode_switching_demo_test.rb +41 -0
  21. data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
  22. data/example/test/integration/dont_dump_tables_test.rb +7 -0
  23. data/example/test/integration/load_rollback_truncate_test.rb +190 -0
  24. data/example/test/integration/mode_switching_demo_test.rb +38 -0
  25. data/example/test/integration/parallel_boops_with_fixtures_test.rb +10 -0
  26. data/example/test/integration/parallel_boops_without_fixtures_test.rb +9 -0
  27. data/example/test/integration/rails_fixtures_double_load_test.rb +10 -0
  28. data/example/test/integration/rails_fixtures_override_test.rb +110 -0
  29. data/example/test/integration/test_data_hooks_test.rb +89 -0
  30. data/example/test/integration/transaction_committing_boops_test.rb +27 -0
  31. data/example/test/test_helper.rb +4 -31
  32. data/lib/generators/test_data/cable_yaml_generator.rb +18 -0
  33. data/lib/generators/test_data/database_yaml_generator.rb +3 -4
  34. data/lib/generators/test_data/environment_file_generator.rb +7 -14
  35. data/lib/generators/test_data/initializer_generator.rb +51 -0
  36. data/lib/generators/test_data/secrets_yaml_generator.rb +19 -0
  37. data/lib/generators/test_data/webpacker_yaml_generator.rb +4 -3
  38. data/lib/test_data.rb +42 -1
  39. data/lib/test_data/active_record_ext.rb +11 -0
  40. data/lib/test_data/config.rb +57 -4
  41. data/lib/test_data/configurators.rb +3 -0
  42. data/lib/test_data/configurators/cable_yaml.rb +25 -0
  43. data/lib/test_data/configurators/environment_file.rb +3 -2
  44. data/lib/test_data/configurators/initializer.rb +26 -0
  45. data/lib/test_data/configurators/secrets_yaml.rb +25 -0
  46. data/lib/test_data/configurators/webpacker_yaml.rb +4 -3
  47. data/lib/test_data/custom_loaders/abstract_base.rb +25 -0
  48. data/lib/test_data/custom_loaders/rails_fixtures.rb +42 -0
  49. data/lib/test_data/dumps_database.rb +55 -5
  50. data/lib/test_data/generator_support.rb +3 -0
  51. data/lib/test_data/inserts_test_data.rb +25 -0
  52. data/lib/test_data/loads_database_dumps.rb +8 -8
  53. data/lib/test_data/log.rb +76 -0
  54. data/lib/test_data/manager.rb +187 -0
  55. data/lib/test_data/rake.rb +20 -9
  56. data/lib/test_data/save_point.rb +34 -0
  57. data/lib/test_data/statistics.rb +31 -0
  58. data/lib/test_data/truncates_test_data.rb +31 -0
  59. data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
  60. data/lib/test_data/version.rb +1 -1
  61. data/script/reset_example_app +18 -0
  62. data/script/test +78 -13
  63. data/test_data.gemspec +1 -1
  64. metadata +36 -4
  65. data/lib/test_data/transactional_data_loader.rb +0 -77
@@ -0,0 +1,94 @@
1
+ # This file was generated by the `rails generate rspec:install` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
16
+ RSpec.configure do |config|
17
+ # rspec-expectations config goes here. You can use an alternate
18
+ # assertion/expectation library such as wrong or the stdlib/minitest
19
+ # assertions if you prefer.
20
+ config.expect_with :rspec do |expectations|
21
+ # This option will default to `true` in RSpec 4. It makes the `description`
22
+ # and `failure_message` of custom matchers include text for helper methods
23
+ # defined using `chain`, e.g.:
24
+ # be_bigger_than(2).and_smaller_than(4).description
25
+ # # => "be bigger than 2 and smaller than 4"
26
+ # ...rather than:
27
+ # # => "be bigger than 2"
28
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
29
+ end
30
+
31
+ # rspec-mocks config goes here. You can use an alternate test double
32
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
33
+ config.mock_with :rspec do |mocks|
34
+ # Prevents you from mocking or stubbing a method that does not exist on
35
+ # a real object. This is generally recommended, and will default to
36
+ # `true` in RSpec 4.
37
+ mocks.verify_partial_doubles = true
38
+ end
39
+
40
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
41
+ # have no way to turn it off -- the option exists only for backwards
42
+ # compatibility in RSpec 3). It causes shared context metadata to be
43
+ # inherited by the metadata hash of host groups and examples, rather than
44
+ # triggering implicit auto-inclusion in groups with matching metadata.
45
+ config.shared_context_metadata_behavior = :apply_to_host_groups
46
+
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
+ # # This allows you to limit a spec run to individual examples or groups
50
+ # # you care about by tagging them with `:focus` metadata. When nothing
51
+ # # is tagged with `:focus`, all examples get run. RSpec also provides
52
+ # # aliases for `it`, `describe`, and `context` that include `:focus`
53
+ # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
54
+ # config.filter_run_when_matching :focus
55
+ #
56
+ # # Allows RSpec to persist some state between runs in order to support
57
+ # # the `--only-failures` and `--next-failure` CLI options. We recommend
58
+ # # you configure your source control system to ignore this file.
59
+ # config.example_status_persistence_file_path = "spec/examples.txt"
60
+ #
61
+ # # Limits the available syntax to the non-monkey patched syntax that is
62
+ # # recommended. For more details, see:
63
+ # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
64
+ # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
65
+ # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
66
+ # config.disable_monkey_patching!
67
+ #
68
+ # # Many RSpec users commonly either run the entire suite or an individual
69
+ # # file, and it's useful to allow more verbose output when running an
70
+ # # individual spec file.
71
+ # if config.files_to_run.one?
72
+ # # Use the documentation formatter for detailed output,
73
+ # # unless a formatter has already been configured
74
+ # # (e.g. via a command-line flag).
75
+ # config.default_formatter = "doc"
76
+ # end
77
+ #
78
+ # # Print the 10 slowest examples and example groups at the
79
+ # # end of the spec run, to help surface which specs are running
80
+ # # particularly slow.
81
+ # config.profile_examples = 10
82
+ #
83
+ # # Run specs in random order to surface order dependencies. If you find an
84
+ # # order dependency and want to debug it, you can fix the order by providing
85
+ # # the seed, which is printed after each run.
86
+ # # --seed 1234
87
+ # config.order = :random
88
+ #
89
+ # # Seed global randomization in this process using the `--seed` CLI option.
90
+ # # Setting this allows you to use `--seed` to deterministically reproduce
91
+ # # test failures related to randomization by passing the same `--seed` value
92
+ # # as the one that triggered the failure.
93
+ # Kernel.srand config.seed
94
+ end
@@ -0,0 +1,4 @@
1
+ FactoryBot.define do
2
+ factory :boop do
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ require "test_helper"
2
+
3
+ class ActiveSupport::TestCase
4
+ def self.test_data_mode(mode)
5
+ case mode
6
+ when :factory_bot
7
+ require "factory_bot_rails"
8
+ include FactoryBot::Syntax::Methods
9
+
10
+ setup do
11
+ TestData.uses_clean_slate
12
+ end
13
+ when :test_data
14
+ setup do
15
+ TestData.uses_test_data
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ class SomeFactoryUsingTest < ActiveSupport::TestCase
22
+ test_data_mode :factory_bot
23
+
24
+ def test_boops
25
+ create(:boop)
26
+
27
+ assert_equal 1, Boop.count
28
+ end
29
+ end
30
+
31
+ class SomeTestDataUsingTest < ActionDispatch::IntegrationTest
32
+ test_data_mode :test_data
33
+
34
+ def test_boops
35
+ assert_equal 10, Boop.count
36
+ end
37
+
38
+ def test_factory_bot_method_is_not_on_this_class
39
+ assert_raises(NameError) { method(:create) }
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ require "test_helper"
2
+
3
+ class BoopsThatBoopBoopsTest < SerializedNonTransactionalTestCase
4
+ def test_each_of_the_boops_has_a_boop
5
+ assert_equal 15, Boop.count
6
+
7
+ Boop.find_each do |boop|
8
+ assert_kind_of Boop, boop.other_boop
9
+ end
10
+ end
11
+
12
+ def test_it_wont_let_you_assign_a_nonsensical_boop
13
+ assert_raise {
14
+ Boop.last.update!(other_boop_id: 2138012)
15
+ }
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ require "test_helper"
2
+
3
+ class DontDumpTablesTest < SerializedNonTransactionalTestCase
4
+ def test_dump_includes_zero_chatty_audit_logs
5
+ assert_equal 0, ChattyAuditLog.count
6
+ end
7
+ end
@@ -0,0 +1,190 @@
1
+ require "test_helper"
2
+
3
+ class LoadRollbackTruncateTest < ActiveSupport::TestCase
4
+ LogMessage = Struct.new(:message, :level, keyword_init: true)
5
+
6
+ def setup
7
+ @last_log = nil
8
+ TestData.log.level = :debug
9
+ TestData.log.writer = ->(message, level) {
10
+ @last_log = LogMessage.new(message: message, level: level)
11
+ }
12
+ end
13
+
14
+ def teardown
15
+ TestData.log.reset
16
+ TestData.statistics.reset
17
+ end
18
+
19
+ def test_loads_data_then_truncates_then_rolls_back_etc
20
+ # Check default state
21
+ assert_equal 0, Boop.count
22
+
23
+ # Now load the dump
24
+ TestData.uses_test_data
25
+ assert_equal 15, Boop.count
26
+ Boop.create!
27
+ assert_equal 16, Boop.count
28
+
29
+ # Next, truncate the boops
30
+ TestData.uses_clean_slate
31
+ assert_equal 0, Boop.count
32
+ Boop.create!
33
+ assert_equal 1, Boop.count
34
+
35
+ # Now roll back to _just after truncate_
36
+ TestData.uses_clean_slate
37
+ assert_equal 0, Boop.count
38
+ Boop.create!
39
+ assert_equal 1, Boop.count
40
+
41
+ # Verify default rollback works after truncate
42
+ TestData.uses_test_data
43
+ assert_equal 15, Boop.count
44
+
45
+ # Verify touching non-test-data tables will also be first rollbacked when truncate is called
46
+ TestData.uninitialize
47
+ good = ChattyAuditLog.create!(message: "I do belong here, because now we're at the start, prior to test_data's purview")
48
+ TestData.uses_test_data
49
+ bad = ChattyAuditLog.create!(message: "I won't belong here after truncate because I'm data that the truncate-calling test wouldn't expect")
50
+ assert_equal 2, ChattyAuditLog.count
51
+
52
+ TestData.uses_clean_slate
53
+
54
+ assert_equal 1, ChattyAuditLog.count
55
+ refute_nil ChattyAuditLog.find_by(id: good.id)
56
+ assert_nil ChattyAuditLog.find_by(id: bad.id)
57
+
58
+ # Warn but load anyway if rolled back to the start and then truncated
59
+ TestData.uninitialize
60
+ TestData.uses_clean_slate
61
+ assert_equal :debug, @last_log.level
62
+ assert_match "TestData.uses_clean_slate was called, but data was not loaded. Loading data", @last_log.message
63
+ assert_equal 0, Boop.count
64
+ TestData.uses_test_data
65
+ assert_equal 15, Boop.count
66
+
67
+ # Chaos: try rolling back outside the gem (one level of extraneous rollback) and verify load recovers
68
+ TestData.uninitialize
69
+ TestData.statistics.reset
70
+ assert_equal 0, TestData.statistics.load_count
71
+ TestData.uses_test_data
72
+ assert_equal 1, TestData.statistics.load_count
73
+ TestData.uses_test_data # Smart enough to not load again
74
+ assert_equal 1, TestData.statistics.load_count
75
+ ActiveRecord::Base.connection.rollback_transaction # Someone might do this!
76
+ TestData.uses_test_data # Still smart enough to not do this
77
+ assert_equal 1, TestData.statistics.load_count
78
+ TestData.uses_test_data # after load savepoint should have been healed with subsequent load call
79
+ assert_equal 15, Boop.count
80
+
81
+ # Chaos: try rolling back outside the gem (one level of extraneous rollback) and verify truncate recovers
82
+ TestData.uninitialize
83
+ TestData.statistics.reset
84
+ assert_equal 0, TestData.statistics.truncate_count
85
+ TestData.uses_test_data
86
+ TestData.uses_clean_slate
87
+ assert_equal 1, TestData.statistics.truncate_count
88
+ TestData.uses_clean_slate
89
+ assert_equal 1, TestData.statistics.truncate_count
90
+ ActiveRecord::Base.connection.rollback_transaction # Someone might do this!
91
+ TestData.uses_clean_slate # Will recover, not take the bait
92
+ assert_equal 1, TestData.statistics.truncate_count
93
+ TestData.uses_clean_slate # after truncate savepoint should have been healed with subsequent truncate call
94
+ assert_equal 0, Boop.count
95
+ TestData.uses_test_data
96
+ assert_equal 15, Boop.count
97
+
98
+ # Chaos: load data then call rollback two times and ensure we're still in a good spot
99
+ TestData.uninitialize
100
+ TestData.statistics.reset
101
+ TestData.uses_test_data
102
+ assert_equal 15, Boop.count
103
+ 2.times do # Two rollbacks means we're back at before_data_load
104
+ ActiveRecord::Base.connection.rollback_transaction
105
+ end
106
+ assert_equal 0, Boop.count
107
+ TestData.uses_test_data # It should successfully load again a second time
108
+ assert_equal 15, Boop.count
109
+ assert_equal 2, TestData.statistics.load_count
110
+
111
+ # Chaos: truncate data then call rollback two times and ensure we're still in a good spot
112
+ TestData.uninitialize
113
+ TestData.statistics.reset
114
+ TestData.uses_clean_slate # will warn-and-load and then truncate
115
+ assert_equal 0, Boop.count
116
+ 2.times do # Two rollbacks means data is loaded but after_data_load savepoint has been lost
117
+ ActiveRecord::Base.connection.rollback_transaction
118
+ end
119
+ assert_equal 15, Boop.count
120
+ assert_equal 1, TestData.statistics.load_count
121
+ assert_equal 1, TestData.statistics.truncate_count
122
+ TestData.uses_clean_slate # should restore the lost after_data_load savepoint and re-truncate
123
+ 3.times do # Three rollbacks means we are at before_data_load again
124
+ ActiveRecord::Base.connection.rollback_transaction
125
+ end
126
+ assert_equal 0, Boop.count
127
+ TestData.uses_test_data
128
+ assert_equal 15, Boop.count
129
+ TestData.uses_clean_slate
130
+ assert_equal 0, Boop.count
131
+ assert_equal 3, TestData.statistics.truncate_count
132
+ assert_equal 2, TestData.statistics.load_count
133
+ end
134
+
135
+ def test_suite_runs_different_tests_in_whatever_order
136
+ # Imagine a test-datay test runs
137
+ test_data_using_test = -> do
138
+ TestData.uses_test_data # before each
139
+ Boop.create!
140
+ assert_equal 16, Boop.count
141
+ end
142
+
143
+ test_data_avoiding_test = -> do
144
+ TestData.uses_clean_slate # before each
145
+ Boop.create!
146
+ assert_equal 1, Boop.count
147
+ end
148
+
149
+ # Run the tests separately:
150
+ 3.times { test_data_using_test.call }
151
+ 3.times { test_data_avoiding_test.call }
152
+
153
+ # Mix and match the tests:
154
+ test_data_using_test.call
155
+ test_data_avoiding_test.call
156
+ test_data_using_test.call
157
+ test_data_avoiding_test.call
158
+ test_data_using_test.call
159
+ test_data_avoiding_test.call
160
+ end
161
+
162
+ def test_calling_truncate_multiple_times_will_return_you_to_truncated_state
163
+ # In the interest of behaving similarly to .load, rollback in case the
164
+ # previous test doesn't have an after_each as you might hope/expect
165
+ 3.times do
166
+ TestData.uses_clean_slate
167
+ Boop.create!
168
+ assert_equal 1, Boop.count
169
+ end
170
+ end
171
+
172
+ def test_other_rollbacks_mess_with_transaction_state_will_debug_you
173
+ TestData.uninitialize
174
+ TestData.statistics.reset
175
+ TestData.uses_test_data
176
+ ActiveRecord::Base.connection.rollback_transaction # data loaded, after_data_load save point destroyed
177
+ TestData.uses_test_data
178
+ assert_equal :debug, @last_log.level # debug only b/c rails fixtures will do this on every after_each if enabled
179
+ assert_match "Recreating the :after_data_load save point", @last_log.message
180
+ assert_equal 1, TestData.statistics.load_count
181
+
182
+ TestData.uses_clean_slate
183
+ ActiveRecord::Base.connection.rollback_transaction # data loaded, after_data_truncate save point destroyed
184
+ TestData.uses_clean_slate
185
+ assert_equal :debug, @last_log.level # debug only b/c rails fixtures will do this on every after_each if enabled
186
+ assert_match "Recreating the :after_data_truncate save point", @last_log.message
187
+ assert_equal 1, TestData.statistics.load_count
188
+ assert_equal 1, TestData.statistics.truncate_count
189
+ end
190
+ end
@@ -0,0 +1,38 @@
1
+ require "test_helper"
2
+
3
+ class ModeSwitchingTestCase < ActiveSupport::TestCase
4
+ self.use_transactional_tests = false
5
+
6
+ def self.test_data_mode(mode)
7
+ if mode == :factory_bot
8
+ require "factory_bot_rails"
9
+ include FactoryBot::Syntax::Methods
10
+
11
+ setup do
12
+ TestData.uses_clean_slate
13
+ end
14
+ elsif mode == :test_data
15
+ setup do
16
+ TestData.uses_test_data
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ class FactoryModeTest < ModeSwitchingTestCase
23
+ test_data_mode :factory_bot
24
+
25
+ def test_boops
26
+ create(:boop)
27
+
28
+ assert_equal 1, Boop.count
29
+ end
30
+ end
31
+
32
+ class TestDataModeTest < ModeSwitchingTestCase
33
+ test_data_mode :test_data
34
+
35
+ def test_boops
36
+ assert_equal 10, Boop.count
37
+ end
38
+ end
@@ -1,5 +1,15 @@
1
1
  require "test_helper"
2
2
 
3
+ class ParallelizedTransactionalFixturefullTestCase < ActiveSupport::TestCase
4
+ parallelize(workers: :number_of_processors)
5
+ self.use_transactional_tests = true
6
+ fixtures :all
7
+
8
+ setup do
9
+ TestData.uses_test_data
10
+ end
11
+ end
12
+
3
13
  class ParallelBoopsWithFixturesTest < ParallelizedTransactionalFixturefullTestCase
4
14
  100.times do |i|
5
15
  test "that boops don't change ##{i}" do
@@ -1,5 +1,14 @@
1
1
  require "test_helper"
2
2
 
3
+ class ParallelizedNonTransactionalFixturelessTestCase < ActiveSupport::TestCase
4
+ parallelize(workers: :number_of_processors)
5
+ self.use_transactional_tests = false
6
+
7
+ setup do
8
+ TestData.uses_test_data
9
+ end
10
+ end
11
+
3
12
  class ParallelBoopsWithoutFixturesTest < ParallelizedNonTransactionalFixturelessTestCase
4
13
  100.times do |i|
5
14
  test "that boops don't change ##{i}" do
@@ -0,0 +1,10 @@
1
+ require "test_helper"
2
+
3
+ class FixturesUsingTest < ActiveSupport::TestCase
4
+ def test_tries_to_load_rails_fixtures_with_test_data
5
+ error = assert_raises(TestData::Error) do
6
+ TestData.uses_rails_fixtures(self)
7
+ end
8
+ assert_match "'TestData.uses_rails_fixtures(self)' depends on Rails' default fixture-loading behavior being disabled by calling 'TestData.prevent_rails_fixtures_from_loading_automatically!' as early as possible (e.g. near the top of your test_helper.rb), but it looks like it was never called", error.message
9
+ end
10
+ end