test_data 0.0.1 → 0.0.2
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/.github/workflows/ruby.yml +45 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile.lock +5 -3
- data/README.md +961 -17
- data/example/Gemfile +3 -0
- data/example/Gemfile.lock +32 -3
- data/example/README.md +2 -22
- data/example/config/credentials.yml.enc +2 -1
- data/example/config/database.yml +2 -0
- data/example/spec/rails_helper.rb +64 -0
- data/example/spec/requests/boops_spec.rb +21 -0
- data/example/spec/spec_helper.rb +94 -0
- data/example/test/factories.rb +4 -0
- data/example/test/integration/better_mode_switching_demo_test.rb +45 -0
- data/example/test/integration/boops_that_boop_boops_test.rb +17 -0
- data/example/test/integration/dont_dump_tables_test.rb +7 -0
- data/example/test/integration/load_rollback_truncate_test.rb +195 -0
- data/example/test/integration/mode_switching_demo_test.rb +48 -0
- data/example/test/integration/parallel_boops_with_fixtures_test.rb +14 -0
- data/example/test/integration/parallel_boops_without_fixtures_test.rb +13 -0
- data/example/test/integration/transaction_committing_boops_test.rb +25 -0
- data/example/test/test_helper.rb +3 -26
- data/lib/generators/test_data/database_yaml_generator.rb +1 -1
- data/lib/generators/test_data/environment_file_generator.rb +0 -14
- data/lib/generators/test_data/initializer_generator.rb +38 -0
- data/lib/generators/test_data/webpacker_yaml_generator.rb +1 -1
- data/lib/test_data.rb +5 -0
- data/lib/test_data/config.rb +25 -2
- data/lib/test_data/configurators.rb +1 -0
- data/lib/test_data/configurators/initializer.rb +25 -0
- data/lib/test_data/dumps_database.rb +31 -4
- data/lib/test_data/loads_database_dumps.rb +7 -7
- data/lib/test_data/log.rb +58 -0
- data/lib/test_data/rake.rb +7 -5
- data/lib/test_data/save_point.rb +34 -0
- data/lib/test_data/statistics.rb +26 -0
- data/lib/test_data/transactional_data_loader.rb +145 -32
- data/lib/test_data/verifies_dumps_are_loadable.rb +4 -4
- data/lib/test_data/version.rb +1 -1
- data/script/reset_example_app +17 -0
- data/script/test +54 -13
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb3ddac1f4153d6b3995975a7b3016089c0f64ef783b45af3b21d5a2ea00f671
|
4
|
+
data.tar.gz: 0d45ac53789a0015c88f6d3f8cc545409c4c42fbd112f63edb1489cc5d603c7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7368531e5dcc41910350809eb2d5a36763f395a749f69dbe85579792cca513fbe5663a0c02c2595009617a63d274caba073572a854cb3f334a07b4b57ce98ff8
|
7
|
+
data.tar.gz: 358f87044733160c2d4ee49b6e8ec1b422cc08e6aa4f0a12a7b47f190899b67ea707159e5cc2e66c307934ad4c1315c45fcbde4666eeb5497b98868afbae31ee
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
|
21
|
+
env:
|
22
|
+
PGHOST: "localhost"
|
23
|
+
PGPORT: "5432"
|
24
|
+
|
25
|
+
services:
|
26
|
+
postgres:
|
27
|
+
image: postgres:13
|
28
|
+
env:
|
29
|
+
POSTGRES_USER: "runner"
|
30
|
+
POSTGRES_HOST_AUTH_METHOD: trust
|
31
|
+
ports: ["5432:5432"]
|
32
|
+
|
33
|
+
strategy:
|
34
|
+
matrix:
|
35
|
+
ruby-version: ['2.7', '3.0']
|
36
|
+
|
37
|
+
steps:
|
38
|
+
- uses: actions/checkout@v2
|
39
|
+
- name: Set up Ruby
|
40
|
+
uses: ruby/setup-ruby@v1
|
41
|
+
with:
|
42
|
+
ruby-version: ${{ matrix.ruby-version }}
|
43
|
+
bundler-cache: true
|
44
|
+
- name: Run tests
|
45
|
+
run: script/test
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
test_data (0.0.
|
4
|
+
test_data (0.0.2)
|
5
5
|
railties (~> 6.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -38,8 +38,10 @@ GEM
|
|
38
38
|
crass (~> 1.0.2)
|
39
39
|
nokogiri (>= 1.5.9)
|
40
40
|
method_source (1.0.0)
|
41
|
+
mini_portile2 (2.5.1)
|
41
42
|
minitest (5.14.4)
|
42
|
-
nokogiri (1.11.3
|
43
|
+
nokogiri (1.11.3)
|
44
|
+
mini_portile2 (~> 2.5.0)
|
43
45
|
racc (~> 1.4)
|
44
46
|
parallel (1.20.1)
|
45
47
|
parser (3.0.1.0)
|
@@ -91,7 +93,7 @@ GEM
|
|
91
93
|
zeitwerk (2.4.2)
|
92
94
|
|
93
95
|
PLATFORMS
|
94
|
-
|
96
|
+
ruby
|
95
97
|
|
96
98
|
DEPENDENCIES
|
97
99
|
minitest
|
data/README.md
CHANGED
@@ -1,35 +1,979 @@
|
|
1
|
-
#
|
1
|
+
# The `test_data` gem
|
2
2
|
|
3
|
-
|
3
|
+
`test_data` is what it says on the tin: a fast & reliable system for managing
|
4
|
+
your Rails application's test data.
|
4
5
|
|
5
|
-
|
6
|
+
The gem serves as both an alternative to
|
7
|
+
[fixtures](https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures)
|
8
|
+
& [factory_bot](https://github.com/thoughtbot/factory_bot), as well a broader
|
9
|
+
workflow for building high-performance test suites that will scale with your
|
10
|
+
application.
|
6
11
|
|
7
|
-
|
12
|
+
What it does:
|
8
13
|
|
9
|
-
|
14
|
+
* Establishes a fourth Rails environment named `test_data` designed to help you
|
15
|
+
create a universe of data your tests can rely on by simply using your
|
16
|
+
application. No Ruby DSL, no YAML files, no precarious approximations of
|
17
|
+
realism: **real data created by your app**
|
18
|
+
|
19
|
+
* Exposes a simple API for loading your test data and cleaning up between
|
20
|
+
tests. Common edge cases are handled gracefully: tests that need access to
|
21
|
+
your data will see it and tests that don't, won't
|
22
|
+
|
23
|
+
* Safeguards your tests from flaky failures and supercharges their speed by
|
24
|
+
providing a sophisticated transaction manager that isolates each test's
|
25
|
+
changes to your data while ensuring your data is only loaded once
|
26
|
+
|
27
|
+
If you've despaired over the seeming inevitability that all Rails test suites
|
28
|
+
will eventually grow to become slow, unreliable, and brittle, then this gem is
|
29
|
+
for you! And even if you're [a factory_bot
|
30
|
+
fan](https://twitter.com/searls/status/1379491813099253762?s=20), we hope you'll
|
31
|
+
be open to the idea that [there might be a better way](
|
32
|
+
#but-we-use-and-like-factory_bot-and-so-i-am-inclined-to-dislike-everything-about-this-gem).
|
33
|
+
|
34
|
+
_[Because the gem is still brand new, it makes a number of
|
35
|
+
[assumptions](#assumptions) and may not work for every project just yet.]_
|
36
|
+
|
37
|
+
## Getting started guide
|
38
|
+
|
39
|
+
This guide will walk you through setting up `test_data` in your application. You
|
40
|
+
might notice that it's more complicated than installing a gem and declaring some
|
41
|
+
default `Widget` attributes! The truth is that designing robust and reliable
|
42
|
+
test data that is inherently complex and takes some thoughtful planning. There
|
43
|
+
are plenty of shortcuts available, but experience has shown they tend to
|
44
|
+
collapse under their own weight as your app scales and your team grows—exactly
|
45
|
+
when fast & reliable tests are most valuable.
|
46
|
+
|
47
|
+
And if you get stuck or need help as you're getting started, please feel free to
|
48
|
+
[ask us for help](https://github.com/testdouble/test_data/discussions/new)!
|
49
|
+
|
50
|
+
### Step 1: Install and initialize `test_data`
|
51
|
+
|
52
|
+
#### Adding the gem
|
53
|
+
|
54
|
+
First, add `test_data` to your Gemfile. Either include it in all groups or add
|
55
|
+
it to the `:development`, `:test`, and (the all new!) `:test_data` gem groups:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
group :development, :test, :test_data do
|
59
|
+
gem "test_data"
|
60
|
+
# … other gems available to development & test
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Since the `test_data` environment is designed to be used similarly to
|
65
|
+
`development` (i.e. with a running server and interacting via a browser) the
|
66
|
+
`:test_data` gem group should probably include everything that's available to
|
67
|
+
the `:development` group.
|
68
|
+
|
69
|
+
#### Configuring the gem and initializing the database
|
70
|
+
|
71
|
+
The gem ships with a number of Rake tasks, including `test_data:install`, which
|
72
|
+
will generate the necessary configuration and initialize a `test_data` database:
|
73
|
+
|
74
|
+
```
|
75
|
+
$ bin/rake test_data:install
|
76
|
+
```
|
77
|
+
|
78
|
+
This should output something like:
|
79
|
+
|
80
|
+
```
|
81
|
+
create config/environments/test_data.rb
|
82
|
+
create config/initializers/test_data.rb
|
83
|
+
insert config/database.yml
|
84
|
+
insert config/webpacker.yml
|
85
|
+
insert config/webpacker.yml
|
86
|
+
Created database 'yourappname_test_data'
|
87
|
+
set_config
|
88
|
+
------------
|
89
|
+
|
90
|
+
(1 row)
|
91
|
+
````
|
92
|
+
|
93
|
+
The purpose of the `test_data` database is to provide a sandbox in which to
|
94
|
+
generate test data by interacting with your app and then dumping the resulting
|
95
|
+
state of the database so that your tests can load it later. Rather than try to
|
96
|
+
imitate realistic data using factories and fixtures (a task which only grows
|
97
|
+
more difficult as your models and their associations increase in complexity),
|
98
|
+
your test data will always be realistic because your real application will have
|
99
|
+
created it!
|
100
|
+
|
101
|
+
The database dumps are meant to be committed in git and versioned alongside your
|
102
|
+
tests over the life of the application. Its schema & data are should be
|
103
|
+
incrementally migrated over time, just like your production database. (As a
|
104
|
+
happy side effect, this means your `test_data` database may help you identify
|
105
|
+
hard-to-catch migration bugs early, before being deployed to production!)
|
106
|
+
|
107
|
+
### Step 2: Create some test data
|
108
|
+
|
109
|
+
Now comes the fun part! It's time to start up your server in the new environment
|
110
|
+
and create some records by interacting with your system.
|
111
|
+
|
112
|
+
#### Running the server (and other commands)
|
113
|
+
|
114
|
+
To run your server against the new `test_data` database, set the `RAILS_ENV`
|
115
|
+
environment variable:
|
116
|
+
|
117
|
+
```
|
118
|
+
$ RAILS_ENV=test_data bin/rails server
|
119
|
+
```
|
120
|
+
|
121
|
+
Because `test_data` creates a full-fledged Rails environment, you can run any
|
122
|
+
number of Rails commands or Rake tasks against its database by setting
|
123
|
+
`RAILS_ENV=test_data`, either in your shell environment or with each command
|
124
|
+
(e.g. `RAILS_ENV=test_data bin/rake db:migrate`)
|
125
|
+
|
126
|
+
_[Aside: If you experience any hiccups in getting your server to work, please
|
127
|
+
[open an issue](https://github.com/testdouble/test_data/issues/new) and let us
|
128
|
+
know—it may present an opportunity to improve the `test_data:configure` task!]_
|
129
|
+
|
130
|
+
#### Create test data by using your app
|
131
|
+
|
132
|
+
Once the app is running, it's time to generate some test data. You'll know how
|
133
|
+
to accomplish this step better than anyone—it's your app, after all!
|
134
|
+
|
135
|
+
Our advice? Spend a little time thoughtfully navigating each feature of your app
|
136
|
+
in order to generate enough data to be _representative_ of what would be needed
|
137
|
+
to test your system's main behaviors (e.g. one `User` for each role, one of each
|
138
|
+
kind of `Order`, etc.), while still being _minimal_ enough that the universe of
|
139
|
+
data will be comprehensible & memorable to yourself and your teammates. It can
|
140
|
+
also help to give new records memorable names, perhaps in keeping with a common
|
141
|
+
theme (easier to refer to "Rafael" than "TestUser #1").
|
142
|
+
|
143
|
+
If you make a mistake, it's perfectly okay to reset the database and start over!
|
144
|
+
Your future tests will be coupled to this data as your application grows and
|
145
|
+
evolves, so it's worth taking the time to ensure the foundation is solid. (But
|
146
|
+
that's not to say everything needs to be perfect; you can always change things
|
147
|
+
or add more data later—you'll just have to update your tests accordingly.)
|
148
|
+
|
149
|
+
### Step 3: Dump your `test_data` database
|
150
|
+
|
151
|
+
Once you have your test data how you want it, dump the schema and data to SQL
|
152
|
+
files:
|
153
|
+
|
154
|
+
```
|
155
|
+
$ bin/rake test_data:dump
|
156
|
+
```
|
157
|
+
|
158
|
+
This will dump three files into `test/support/test_data`:
|
159
|
+
|
160
|
+
* Schema DDL in `schema.sql`
|
161
|
+
|
162
|
+
* Test data in `data.sql`
|
163
|
+
|
164
|
+
* Non-test data (`ar_internal_metadata` and `schema_migrations` by default) in
|
165
|
+
`non_test_data.sql`
|
166
|
+
|
167
|
+
These paths can be overridden with [TestData.config](#testdataconfig) method.
|
168
|
+
Additional details can be found in the [test_data:dump](#test_datadump)
|
169
|
+
Rake task reference.
|
170
|
+
|
171
|
+
Once you've made your initial set of dumps, briefly inspect them and—if
|
172
|
+
everything looks good—commit them. (And if the files are gigantic or full of
|
173
|
+
noise, you might find [these ideas
|
174
|
+
helpful](#are-you-sure-i-should-commit-these-sql-dumps-theyre-way-too-big)).
|
175
|
+
|
176
|
+
_[Feel weird to dump and commit SQL files? That's okay! It's [healthy to be
|
177
|
+
skeptical](https://twitter.com/searls/status/860553435116187649?s=20) whenever
|
178
|
+
you're asked to commit a generated file! Remember that the `test_data`
|
179
|
+
environment exists only for creating your test data. Your tests will, in turn,
|
180
|
+
load the SQL dump of your data into the familiar `test` database, and things
|
181
|
+
will proceed just as if you'd been loading [Rails' built-in
|
182
|
+
fixtures](https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures)
|
183
|
+
from a set of YAML files—the major difference being how the data is authored.]_
|
184
|
+
|
185
|
+
### Step 4: Load your data in your tests
|
186
|
+
|
187
|
+
Now that you've dumped the contents of your `test_data` database, you can start
|
188
|
+
writing tests that rely on this test data.
|
189
|
+
|
190
|
+
To accomplish this, you'll likely want to add hooks to run before & after each
|
191
|
+
test—first to load your test data and then to rollback any changes made by the
|
192
|
+
test. The `test_data` gem accomplishes this with its
|
193
|
+
[TestData.load](#testdataload) and [TestData.rollback](#testdatarollback)
|
194
|
+
methods.
|
195
|
+
|
196
|
+
If you're using (Rails' default)
|
197
|
+
[Minitest](https://github.com/seattlerb/minitest) and want to include your test
|
198
|
+
data with every test, you can add these hooks to `ActiveSupport::TestCase`:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
class ActiveSupport::TestCase
|
202
|
+
def setup
|
203
|
+
TestData.load
|
204
|
+
end
|
205
|
+
|
206
|
+
def teardown
|
207
|
+
TestData.rollback
|
208
|
+
end
|
209
|
+
end
|
210
|
+
```
|
211
|
+
|
212
|
+
If you use [RSpec](https://rspec.info), you can accomplish the same thing with
|
213
|
+
global `before(:each)` and `after(:each)` hooks in your `rails_helper.rb` file:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
RSpec.configure do |config|
|
217
|
+
config.before(:each) do
|
218
|
+
TestData.load
|
219
|
+
end
|
220
|
+
|
221
|
+
config.after(:each) do
|
222
|
+
TestData.rollback
|
223
|
+
end
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
That should be all you need to have access to your test data in all of your
|
228
|
+
tests, along with the speed and data integrity of wrapping each test in an
|
229
|
+
always-rolled-back transaction. For more information on how all this works, see
|
230
|
+
the [API reference](#api-reference).
|
231
|
+
|
232
|
+
If you _don't_ want all of your Rails-aware tests to see this test data (suppose
|
233
|
+
you have existing tests that use factories or fixtures instead), you probably
|
234
|
+
want to use [TestData.truncate](#testdatatruncate) to clear data generated by
|
235
|
+
this gem out before they run. You might do that by defining two test types:
|
10
236
|
|
11
237
|
```ruby
|
12
|
-
|
238
|
+
# Tests using data created by `test_data`
|
239
|
+
class TestDataTestCase < ActiveSupport::TestCase
|
240
|
+
def setup
|
241
|
+
TestData.load
|
242
|
+
end
|
243
|
+
|
244
|
+
def teardown
|
245
|
+
TestData.rollback
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Tests using data created by `factory_bot`
|
250
|
+
class FactoryBotTestCase < ActiveSupport::TestCase
|
251
|
+
include FactoryBot::Syntax::Methods
|
252
|
+
|
253
|
+
def setup
|
254
|
+
TestData.truncate
|
255
|
+
end
|
256
|
+
|
257
|
+
def teardown
|
258
|
+
TestData.rollback(:after_data_truncate)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
```
|
263
|
+
|
264
|
+
For more thoughts on migrating to `test_data` when you have existing tests,
|
265
|
+
[some ideas are discussed
|
266
|
+
here](#we-already-have-thousands-of-tests-that-depend-on-rails-fixtures-or-factory_bot-can-we-start-using-test_data-without-throwing-them-away-and-starting-over).
|
267
|
+
|
268
|
+
### Step 5: Keeping your test data up-to-date
|
269
|
+
|
270
|
+
Because your app relies on its tests and your tests rely on their test data,
|
271
|
+
your test data needs to be maintained for the entire life of your application.
|
272
|
+
Fortunately, and because production databases need the same thing, we already
|
273
|
+
have a fantastic tool for the job: [Rails
|
274
|
+
migrations](https://guides.rubyonrails.org/active_record_migrations.html). If
|
275
|
+
your migrations are resilient enough for your production data, they should also
|
276
|
+
be able to keep your `test_data` database up-to-date.
|
277
|
+
|
278
|
+
Whenever you update your schema, migrate your data, or add a feature that
|
279
|
+
necessitates the creation of more test data, you'll need to update your test
|
280
|
+
data. Here's a rough outline to updating your `test_data` database:
|
281
|
+
|
282
|
+
1. If your local `test_data` database is out-of-date with your latest SQL dump
|
283
|
+
files, drop it with `rake test_data:drop_database`
|
284
|
+
|
285
|
+
2. Load your schema & data into the database with `rake test_data:load`
|
286
|
+
|
287
|
+
3. Run any pending migrations with `RAILS_ENV=test_data bin/rake db:migrate`
|
288
|
+
|
289
|
+
4. If you need to create any additional data, start up the server
|
290
|
+
(`RAILS_ENV=test_data bin/rails s`), just like [Step
|
291
|
+
2](#step-2-create-some-test-data)
|
292
|
+
|
293
|
+
5. Export your newly-updated `test_data` database with `rake test_data:dump`
|
294
|
+
|
295
|
+
6. Ensure your tests are passing and commit the resulting SQL files
|
296
|
+
|
297
|
+
It's important to keep in mind that your test data SQL dumps are a shared,
|
298
|
+
generated resource among your team (just like a `structure.sql` or `schema.rb`
|
299
|
+
file). As a result, if your team doesn't integrate code frequently or if the
|
300
|
+
test data experiences a lot of churn in coincident feature branches, you'd be
|
301
|
+
right to be concerned that [the resulting merge conflicts could become
|
302
|
+
significant](#how-will-i-handle-merge-conflicts-in-these-sql-files-if-i-have-lots-of-people-working-on-lots-of-feature-branches-all-adding-to-the-test_data-database-dumps),
|
303
|
+
so sweeping changes should be made deliberately and in collaboration with other
|
304
|
+
contributors.
|
305
|
+
|
306
|
+
_[Aside: some Rails teams are averse to using migrations to migrate data as well
|
307
|
+
as schemas, instead preferring one-off scripts and tasks. You'll have an easier
|
308
|
+
time of things if you use migrations for both schema and data changes. Here are
|
309
|
+
some notes on [how to write data migrations
|
310
|
+
safely](https://blog.testdouble.com/posts/2014-11-04-healthy-migration-habits/#habit-4-dont-reference-models).]_
|
311
|
+
|
312
|
+
## Rake Task Reference
|
313
|
+
|
314
|
+
### test_data:install
|
315
|
+
|
316
|
+
A meta-task that runs [test_data:configure](#test_dataconfigure) and [test_data:initialize](#test_datainitialize).
|
317
|
+
|
318
|
+
### test_data:configure
|
319
|
+
|
320
|
+
This task runs several generators:
|
321
|
+
|
322
|
+
* `config/environments/test_data.rb` - As you may know, Rails ships with
|
323
|
+
`development`, `test`, and `production` environments defined by default. But
|
324
|
+
you can [actually define custom
|
325
|
+
environments](https://guides.rubyonrails.org/configuring.html#creating-rails-environments),
|
326
|
+
too! This gem adds a new `test_data` environment and database that's intended
|
327
|
+
to be used to create and dump your test data. This new environment file loads
|
328
|
+
your `development` environment's configuration and disables migration schema
|
329
|
+
dumps so that you can run migrations of your `test_data` database without
|
330
|
+
affecting your app's `schema.rb` or `structure.sql`.
|
331
|
+
|
332
|
+
* `config/initializers/test_data.rb` - Calls [TestData.config](#testdataconfig)
|
333
|
+
with an empty block and comments documenting the currently-available options
|
334
|
+
and their default values
|
335
|
+
|
336
|
+
* `config/database.yml` - This generator adds a new `test_data` section to your
|
337
|
+
database configuration, named with the same scheme as your other databases
|
338
|
+
(e.g. `your_app_test_data`). If your configuration resembles Rails' generated
|
339
|
+
database.yml and has a working `&default` alias, then this should "just work"
|
340
|
+
|
341
|
+
* `config/webpacker.yml` - The gem has nothing to do with web assets or
|
342
|
+
webpacker, but webpacker will display some prominent warnings or errors if it
|
343
|
+
is loaded without a configuration entry for the currently-running environment,
|
344
|
+
so this generator defines an alias based on your `development` config and then
|
345
|
+
defines `test_data` as extending it
|
346
|
+
|
347
|
+
### test_data:initialize
|
348
|
+
|
349
|
+
This task gets your local `test_data` database up-and-running, either from a set
|
350
|
+
of dump files (if they already exist), or by loading your schema and running
|
351
|
+
your seed file. Specifically:
|
352
|
+
|
353
|
+
1. Creates the `test_data` environment's database, if it doesn't already exist
|
354
|
+
|
355
|
+
2. Ensures the database is non-empty to preserve data integrity (run
|
356
|
+
`test_data:drop_database` first if it contains outdated test data)
|
357
|
+
|
358
|
+
3. Checks to see if a dump of the database already exists (by default, stored in
|
359
|
+
`test/support/test_data/`)
|
360
|
+
|
361
|
+
* If dumps do exist, it invokes `test_data:load` to load them into the
|
362
|
+
database
|
363
|
+
|
364
|
+
* Otherwise, it invokes the task `db:schema:load` and `db:seed` (similar to
|
365
|
+
the `db:setup` task)
|
366
|
+
|
367
|
+
### test_data:dump
|
368
|
+
|
369
|
+
This task is designed to be run after you've created or updated your test data
|
370
|
+
and you want to run tests against it. The task creates several plain SQL dumps
|
371
|
+
from your `test_data` environment's database:
|
372
|
+
|
373
|
+
* A schema-only dump, by default in `test/support/test_data/schema.sql`
|
374
|
+
|
375
|
+
* A data-only dump of records you want to be loaded in your tests, by default in
|
376
|
+
`test/support/test_data/data.sql`
|
377
|
+
|
378
|
+
* A data-only dump of records that you *don't* want loaded in your tests in
|
379
|
+
`test/support/test_data/non_test_data.sql` (by default, this includes Rails'
|
380
|
+
internal tables: `ar_internal_metadata` and `schema_migrations`, configurable
|
381
|
+
with [TestData.config](#testdataconfig)'s `non_test_data_tables`)
|
382
|
+
|
383
|
+
Each of these files are designed to be committed and versioned with the rest of
|
384
|
+
your application. [TestData.config](#testdataconfig) includes several
|
385
|
+
options to control which tables are exported into which group.
|
386
|
+
|
387
|
+
### test_data:load
|
388
|
+
|
389
|
+
This task will load your SQL dumps into your `test_data` database by:
|
390
|
+
|
391
|
+
1. Verifying the `test_data` environment's database is empty (creating it if it
|
392
|
+
doesn't exist)
|
393
|
+
|
394
|
+
2. Verifying that your schema, test data, and non-test data SQL dumps can be
|
395
|
+
found at the configured paths
|
396
|
+
|
397
|
+
3. Loading the dumps into the `test_data` database
|
398
|
+
|
399
|
+
4. Warning if there are pending migrations that haven't been run yet
|
400
|
+
|
401
|
+
If there are pending migrations, you'll probably want to run them and then
|
402
|
+
dump & commit your test data so that they're all up-to-date:
|
403
|
+
|
404
|
+
```
|
405
|
+
$ RAILS_ENV=test_data bin/rake db:migrate
|
406
|
+
$ bin/rake test_data:dump
|
13
407
|
```
|
14
408
|
|
15
|
-
|
409
|
+
### test_data:create_database
|
410
|
+
|
411
|
+
This task will create the `test_data` environment's database if it does not
|
412
|
+
already exist. It also enhances Rails' `db:create` task so that `test_data` is
|
413
|
+
created along with `development` and `test`.
|
414
|
+
|
415
|
+
### test_data:drop_database
|
416
|
+
|
417
|
+
This task will drop the `test_data` environment's database if it exists. It also
|
418
|
+
enhances Rails' `db:drop` task so that `test_data` is dropped along with
|
419
|
+
`development` and `test`.
|
420
|
+
|
421
|
+
## API Reference
|
422
|
+
|
423
|
+
### TestData.config
|
424
|
+
|
425
|
+
The generated `config/initializers/test_data.rb` initializer will include a call
|
426
|
+
to `TestData.config`, which takes a block that yields a mutable configuration
|
427
|
+
object (similar to `Rails.application.config`):
|
16
428
|
|
17
|
-
|
429
|
+
```ruby
|
430
|
+
TestData.config do |config|
|
431
|
+
# Where to store SQL dumps of the test_data database schema
|
432
|
+
# config.schema_dump_path = "test/support/test_data/schema.sql"
|
433
|
+
|
434
|
+
# Where to store SQL dumps of the test_data database test data
|
435
|
+
# config.data_dump_path = "test/support/test_data/data.sql"
|
436
|
+
|
437
|
+
# Where to store SQL dumps of the test_data database non-test data
|
438
|
+
# config.non_test_data_dump_path = "test/support/test_data/non_test_data.sql"
|
439
|
+
|
440
|
+
# Tables whose data shouldn't be loaded into tests.
|
441
|
+
# ("ar_internal_metadata" and "schema_migrations" are always excluded)
|
442
|
+
# config.non_test_data_tables = []
|
443
|
+
|
444
|
+
# Tables whose data should be excluded from all dumps (does not affect schema DDL)
|
445
|
+
# config.dont_dump_these_tables = []
|
446
|
+
|
447
|
+
# Tables whose data should be truncated by TestData.truncate
|
448
|
+
# If left as `nil`, all tables inserted into by the SQL file at
|
449
|
+
# `data_dump_path` will be truncated
|
450
|
+
# config.truncate_these_test_data_tables = nil
|
451
|
+
|
452
|
+
# Log level (valid values: [:debug, :info, :warn, :error, :quiet])
|
453
|
+
# Can also be set with env var TEST_DATA_LOG_LEVEL
|
454
|
+
# config.log_level = :info
|
455
|
+
end
|
456
|
+
```
|
18
457
|
|
19
|
-
|
458
|
+
### TestData.load
|
20
459
|
|
21
|
-
|
460
|
+
This is the method designed to be used by your tests to load your test data
|
461
|
+
into your `test` database so that your tests can rely on it.
|
22
462
|
|
23
|
-
|
463
|
+
#### Loading with the speed & safety of transaction savepoints
|
24
464
|
|
25
|
-
|
465
|
+
For the sake of speed and integrity, `TestData.load` is designed to take
|
466
|
+
advantage of nested transactions ([Postgres
|
467
|
+
savepoints](https://www.postgresql.org/docs/current/sql-savepoint.html)). By
|
468
|
+
default, data is loaded in a transaction and intended to be rolled back to the
|
469
|
+
point _immediately after_ the data was imported after each test. This way, your
|
470
|
+
test suite only pays the cost of importing the SQL file once, but each of your
|
471
|
+
tests can enjoy a clean slate free of potential data pollution from prior tests.
|
472
|
+
(This is similar to, but separate from, Rails fixtures'
|
473
|
+
[use_transactional_tests](https://edgeguides.rubyonrails.org/testing.html#testing-parallel-transactions)
|
474
|
+
option.)
|
26
475
|
|
27
|
-
|
476
|
+
To help think through the method's behavior, the method nicknames its
|
477
|
+
transactions `:before_data_load` and `:after_data_load`. The first time you call
|
478
|
+
`TestData.load`:
|
479
|
+
|
480
|
+
1. Starts the `:before_data_load` transaction
|
481
|
+
2. Executes the SQL found in the data dump (e.g.
|
482
|
+
`test/support/test_data/data.sql`) to insert your test data
|
483
|
+
3. Starts the `:after_data_load` transaction
|
484
|
+
|
485
|
+
If the method is called and the `:after_data_load` savepoint is already active
|
486
|
+
(indicating that the data is loaded), the method rolls back to
|
487
|
+
`:after_data_load`, inferring that the user's intention is to have a clean load
|
488
|
+
of the test data.
|
489
|
+
|
490
|
+
_[As an additional safeguard, in case a rollback is triggered unexpectedly (i.e.
|
491
|
+
calling `rollback_transaction` on `ActiveRecord::Base.connection` instead of via
|
492
|
+
`TestData.rollback`), `test_data` writes a memo that the data is loaded in
|
493
|
+
`ar_internal_metadata`. `TestData.load` uses this memo to detect this issue and
|
494
|
+
will recreate the `:after_data_load` savepoint rather than attempt to
|
495
|
+
erroneously reload your SQL data dump.]_
|
496
|
+
|
497
|
+
#### Loading without transactions
|
498
|
+
|
499
|
+
For most cases, we strongly recommend using the default transactional testing
|
500
|
+
strategy, both because it's faster and because it reduces the risk of test
|
501
|
+
pollution. However, you may need to commit your test data if the data needs to
|
502
|
+
be loaded by multiple processes or over multiple connections.
|
503
|
+
|
504
|
+
If you need to load the test data and commit it to the database, simply call
|
505
|
+
`TestData.load(transactions: false)`.
|
506
|
+
|
507
|
+
Once committed, figuring out when and how to clear it out after each test run
|
508
|
+
then becomes an additional responsibility. Many folks use
|
509
|
+
[database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) for
|
510
|
+
this, while this gem offers a rudimentary
|
511
|
+
[TestData.truncate](https://github.com/testdouble/test_data#testdatatruncate)
|
512
|
+
method that can be passed `transactions: false` and which may be sufficient.
|
513
|
+
|
514
|
+
You might imagine something like this if you were loading the data just once for
|
515
|
+
the full run of a test suite:
|
516
|
+
|
517
|
+
```ruby
|
518
|
+
RSpec.configure do |config|
|
519
|
+
config.before :all do
|
520
|
+
TestData.load(transactions: false)
|
521
|
+
end
|
522
|
+
|
523
|
+
config.after :all do
|
524
|
+
TestData.truncate(transactions: false)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
```
|
528
|
+
|
529
|
+
Note that subsequent `TestData.load(transactions: false)` calls won't be able to
|
530
|
+
detect whether the data is already loaded and will try to re-insert the data,
|
531
|
+
which will almost certainly result in primary key conflicts.
|
532
|
+
|
533
|
+
### TestData.rollback
|
534
|
+
|
535
|
+
Because the gem loads your data in a transaction, it makes it easy to rollback
|
536
|
+
to any of its defined savepoints (`:before_data_load`, `:after_data_load`, and
|
537
|
+
`:after_data_truncate`). In most cases you'll want to roll back to
|
538
|
+
`:after_data_load` after each test, and that's what `TestData.rollback` will do
|
539
|
+
when called without an argument. More details on rolling back to each of the
|
540
|
+
gem's savepoints follows below.
|
541
|
+
|
542
|
+
#### Rolling back to after the data was loaded
|
543
|
+
|
544
|
+
When `TestData.rollback` is passed no arguments or called more explicitly as
|
545
|
+
`TestData.rollback(:after_data_load)`, the method will rollback to the
|
546
|
+
`:after_data_load` transaction savepoint taken immediately after the SQL dump
|
547
|
+
was loaded. As a result, it is intended to be run after each test (e.g. in an
|
548
|
+
`after(:each)` or `teardown`), to undo any changes made by the test.
|
549
|
+
|
550
|
+
(Calling `TestData.rollback` when no `:after_data_load` save point is active is
|
551
|
+
a no-op.)
|
552
|
+
|
553
|
+
#### Rolling back to before test data was loaded
|
554
|
+
|
555
|
+
If some tests rely on data loaded by `TestData.load` and you have other tests
|
556
|
+
that depend on that data _not being there_, you probably want to call
|
557
|
+
[TestData.truncate](#testdatatruncate). But if that won't work for your needs,
|
558
|
+
you can rewind to the moment just before your test data was loaded by calling
|
559
|
+
`TestData.rollback(:before_data_load)`.
|
560
|
+
|
561
|
+
(Calling `TestData.rollback` when no `:before_data_load` save point is active is
|
562
|
+
a no-op.)
|
563
|
+
|
564
|
+
**⚠️ Warning:** Repeatedly loading and rolling back to `:before_data_load` is
|
565
|
+
expensive! If your test suite calls `TestData.rollback(:before_data_load)`
|
566
|
+
multiple times, it's likely you're re-loading your (possibly large) SQL file of
|
567
|
+
test data more times than is necessary. Consider using
|
568
|
+
[TestData.truncate](#testdatatruncate) to achieve the same goal with faster
|
569
|
+
performance. Failing that, it might be preferable to partition your test suite
|
570
|
+
so that similar tests are run in separate groups (as opposed to in a fully
|
571
|
+
random or arbitrary order) to avoid repeatedly thrashing between rollbacks and
|
572
|
+
reloads. This partitioning could be accomplished by either configuring your test
|
573
|
+
runner or by running separate test commands for each group of tests.
|
574
|
+
|
575
|
+
#### Rolling back to after test data was truncated
|
576
|
+
|
577
|
+
If some of your tests call [TestData.truncate](#testdatatruncate) to clear out
|
578
|
+
your test data after it's been loaded, then you will likely want to run
|
579
|
+
`TestData.rollback(:after_data_truncate)` after each of them. This will rewind
|
580
|
+
your test database's state to when those tables were first truncated—effectively
|
581
|
+
re-cleaning the slate for the next test.
|
582
|
+
|
583
|
+
(Calling `TestData.rollback` when no `:after_data_truncate` save point is active
|
584
|
+
is a no-op.)
|
585
|
+
|
586
|
+
### TestData.truncate
|
587
|
+
|
588
|
+
Do you have some tests that _shouldn't_ access your test data? Or did some
|
589
|
+
existing tests started failing after `test_data` was added? If you want to reset
|
590
|
+
the state of your `test` database to support these tests, you have two options:
|
591
|
+
(1) `TestData.rollback(:before_data_load)` and (2) `TestData.truncate`. As
|
592
|
+
discussed above, the former will do the job, but may necessitate repetitive,
|
593
|
+
slow reloads of your test data SQL file. `TestData.truncate`, meanwhile,
|
594
|
+
truncates all the tables that `TestData.load` inserted into and then creates
|
595
|
+
a savepoint nicknamed `:after_data_truncate`.
|
596
|
+
|
597
|
+
Most often, you'll want to call `TestData.truncate` before each test that
|
598
|
+
should _not_ have access to your test data created with this gem. After each
|
599
|
+
such test, it can clean up by calling `TestData.rollback(:after_data_truncate)`:
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
class CleanSlateTest < ActiveDispatch::IntegrationTest
|
603
|
+
def setup do
|
604
|
+
TestData.truncate
|
605
|
+
end
|
606
|
+
|
607
|
+
def teardown do
|
608
|
+
TestData.rollback(:after_data_truncate)
|
609
|
+
end
|
610
|
+
end
|
611
|
+
```
|
612
|
+
|
613
|
+
By default, all tables for which there is an `INSERT INTO` statement in your
|
614
|
+
test data SQL dump will be truncated, but you can specify which tables should be
|
615
|
+
truncated yourself by setting the `truncate_these_test_data_tables` property on
|
616
|
+
[TestData.config](#testdataconfig) to an array of table names.
|
617
|
+
|
618
|
+
Because tests are usually run in a random order, some fault tolerance is built
|
619
|
+
in:
|
620
|
+
|
621
|
+
* If one test calls `TestData.rollback(:after_data_truncate)` in `teardown`, and
|
622
|
+
the next test calls `TestData.load` in `setup`, the gem will do the
|
623
|
+
probably-intended thing and rollback to `:after_data_load` so that the data is
|
624
|
+
available
|
625
|
+
|
626
|
+
* If a test calls `TestData.truncate` and the `:after_data_truncate` savepoint
|
627
|
+
is rolled back outside the `TestData.rollback` method (e.g.
|
628
|
+
`ActiveRecord::Base.connection.rollback_transaction`), the method will first
|
629
|
+
check a memo written to `ar_internal_metadata` and recreate the
|
630
|
+
`:after_data_truncate` savepoint
|
631
|
+
|
632
|
+
#### If you're not using transactions
|
633
|
+
|
634
|
+
Just [like TestData.load](#loading-without-transactions), you can call
|
635
|
+
`TestData.truncate(transactions: false)` and it'll commit the truncation.
|
636
|
+
|
637
|
+
## Assumptions
|
638
|
+
|
639
|
+
The `test_data` gem is still brand new and doesn't cover every use case just
|
640
|
+
yet. Here are some existing assumptions and limitations:
|
641
|
+
|
642
|
+
* You're using Postgres
|
643
|
+
|
644
|
+
* You're using Rails 6 or higher
|
645
|
+
|
646
|
+
* Your app does not rely on Rails' [multi-database
|
647
|
+
support](https://guides.rubyonrails.org/active_record_multiple_databases.html)
|
648
|
+
|
649
|
+
* Your app has the binstubs `bin/rake` and `bin/rails` that Rails generates and
|
650
|
+
they work (you can regenerate them with `rails app:update:bin`)
|
651
|
+
|
652
|
+
* Your `database.yml` defines a `&default` alias from which to extend the
|
653
|
+
`test_data` database configuration (if it lacks one, you'll need specify the
|
654
|
+
database's configuration on your own)
|
655
|
+
|
656
|
+
## Fears, Uncertainties, and Doubts
|
657
|
+
|
658
|
+
### But we use and like `factory_bot` and so I am inclined to dislike everything about this gem!
|
659
|
+
|
660
|
+
If you use `factory_bot` and all of these are true:
|
661
|
+
|
662
|
+
* Your integration tests are super fast and not getting significantly slower
|
663
|
+
over time
|
664
|
+
|
665
|
+
* Innocuous changes to factories rarely result in unrelated test failures
|
666
|
+
that—rather than indicating a bug in the production code—instead require that
|
667
|
+
each of those tests be analyzed & updated to get them passing again
|
668
|
+
|
669
|
+
* The number of associated records generated between your most-used factories
|
670
|
+
are representative of production data, as opposed to generating a sprawling
|
671
|
+
hierarchy of models, as if your test just ordered "one of everything" off the
|
672
|
+
menu
|
673
|
+
|
674
|
+
* Your default factories generate models that resemble real records created by
|
675
|
+
your production application, as opposed to resembling the
|
676
|
+
sum-of-all-edge-cases with every boolean flag enabled and optional attribute
|
677
|
+
set
|
678
|
+
|
679
|
+
* You've avoided mitigating the above problems with confusingly-named and
|
680
|
+
confidence-eroding nested factories with names like `:user`, `:basic_user`,
|
681
|
+
`:lite_user`, and `:plain_user_no_associations_allowed`
|
682
|
+
|
683
|
+
If none of these things are true, then congratulations! You are using
|
684
|
+
`factory_bot` with great success! Unfortunately, in our experience, this outcome
|
685
|
+
is exceedingly rare, especially for large and long-lived applications.
|
686
|
+
|
687
|
+
However, if any of the above issues resonate with your experience using
|
688
|
+
`factory_bot`: these are the sorts of failure modes the `test_data` gem was
|
689
|
+
designed to address. We hope you'll consider trying it with an open mind. At the
|
690
|
+
same time, we acknowledge that large test suites can't be rewritten and migrated
|
691
|
+
to a different source of test data overnight—nor should they be! See our notes
|
692
|
+
on [migrating to `test_data`
|
693
|
+
incrementally](#we-already-have-thousands-of-tests-that-depend-on-rails-fixtures-or-factory_bot-can-we-start-using-test_data-without-throwing-them-away-and-starting-over)
|
694
|
+
|
695
|
+
### How will I handle merge conflicts in these SQL files if I have lots of people working on lots of feature branches all adding to the `test_data` database dumps?
|
696
|
+
|
697
|
+
In a word: carefully!
|
698
|
+
|
699
|
+
First, in terms of expectations-setting, you should expect your test data SQL
|
700
|
+
dumps to churn at roughly the same rate as your schema: lots of changes up
|
701
|
+
front, but tapering off as the application stabilizes.
|
702
|
+
|
703
|
+
If your schema isn't changing frequently and you're not running data migrations
|
704
|
+
against production very often, it might make the most sense to let this concern
|
705
|
+
present itself as a real problem before attempting to solve it, as you're likely
|
706
|
+
to find that other best-practices around collaboration and deployment (frequent
|
707
|
+
merges, continuous integration, coordinating breaking changes) will also manage
|
708
|
+
this risk. The reason that the dumps are stored as plain SQL (aside from the
|
709
|
+
fact that git's text compression is very good) is to make merge conflicts with
|
710
|
+
other branches feasible, if not entirely painless.
|
711
|
+
|
712
|
+
However, if your app is in the very initial stages of development and you're
|
713
|
+
making breaking changes to your schema very frequently, our best advice is to
|
714
|
+
hold off a bit on writing _any_ integration tests that depend on shared sources
|
715
|
+
of test data, as they'll be more likely to frustrate your ability to rapidly
|
716
|
+
iterate than detect bugs. Once you you have a reasonably stable feature working
|
717
|
+
end-to-end, that's a good moment to start adding integration tests (and thus
|
718
|
+
pulling in a test data gem like this one to help you).
|
719
|
+
|
720
|
+
### We already have thousands of tests that depend on Rails fixtures or factory_bot; can we start using this gem without throwing them away and starting over?
|
721
|
+
|
722
|
+
Yes, `test_data` was written with this sort of incremental transition in mind!
|
723
|
+
|
724
|
+
Our strongest recommendation is to write each test against only a single source
|
725
|
+
of test data. Integration tests inevitably become coupled to the data that's
|
726
|
+
available to them, and if a test has access to fixtures, records created by a
|
727
|
+
factory, and a `test_data` SQL dump, it is likely to unintentionally become
|
728
|
+
dependent on all three. This could result in the test having more ways to fail
|
729
|
+
than necessary and make it harder to simplify your test data strategy later.
|
730
|
+
Instead, consider explicitly opting into a single type of test data for each
|
731
|
+
test.
|
732
|
+
|
733
|
+
The rest comes down to how you want to organize your tests, as discussed below.
|
734
|
+
|
735
|
+
#### Approach 1: test-data-specific test subclasses
|
736
|
+
|
737
|
+
If you're currently using `factory_bot`, you might already have something like
|
738
|
+
this in your `test_helper.rb` file:
|
739
|
+
|
740
|
+
```ruby
|
741
|
+
class ActiveSupport::TestCase
|
742
|
+
include FactoryBot::Syntax::Methods
|
743
|
+
end
|
744
|
+
```
|
745
|
+
|
746
|
+
To avoid including these methods with every Rails-aware test, you might consider
|
747
|
+
pushing down new classes for each source of test data:
|
748
|
+
|
749
|
+
```ruby
|
750
|
+
# Tests using data created by `test_data`
|
751
|
+
class TestDataTestCase < ActiveSupport::TestCase
|
752
|
+
def setup
|
753
|
+
TestData.load
|
754
|
+
end
|
755
|
+
|
756
|
+
def teardown
|
757
|
+
TestData.rollback
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
# Tests using data created by `factory_bot`
|
762
|
+
class FactoryBotTestCase < ActiveSupport::TestCase
|
763
|
+
include FactoryBot::Syntax::Methods
|
764
|
+
|
765
|
+
def setup
|
766
|
+
TestData.truncate
|
767
|
+
end
|
768
|
+
|
769
|
+
def teardown
|
770
|
+
TestData.rollback(:after_data_truncate)
|
771
|
+
end
|
772
|
+
end
|
773
|
+
```
|
774
|
+
|
775
|
+
From there, the class that each test extends will indicate which test data
|
776
|
+
strategy it uses.
|
777
|
+
|
778
|
+
This approach may work in simple cases, but won't be well-suited to if you're
|
779
|
+
already extending any of the half-dozen subclasses of `ActiveSupport::TestCase`
|
780
|
+
provided by Rails (or their analogues from
|
781
|
+
[rspec-rails](https://github.com/rspec/rspec-rails)).
|
782
|
+
|
783
|
+
#### Approach 2: write a helper method to set things up for you
|
784
|
+
|
785
|
+
Every situation will be different, but one strategy that suits a lot of
|
786
|
+
circumstances would be to write a method to declare and configure the test data
|
787
|
+
strategy for the current test.
|
788
|
+
|
789
|
+
Taking from [this
|
790
|
+
example](/example/test/integration/better_mode_switching_demo_test.rb) test, you
|
791
|
+
could implement a class method like this:
|
792
|
+
|
793
|
+
```ruby
|
794
|
+
class ActiveSupport::TestCase
|
795
|
+
def self.test_data_mode(mode)
|
796
|
+
case mode
|
797
|
+
when :factory_bot
|
798
|
+
require "factory_bot_rails"
|
799
|
+
include FactoryBot::Syntax::Methods
|
800
|
+
|
801
|
+
setup do
|
802
|
+
TestData.truncate
|
803
|
+
end
|
804
|
+
|
805
|
+
teardown do
|
806
|
+
TestData.rollback(:after_data_truncate)
|
807
|
+
end
|
808
|
+
when :test_data
|
809
|
+
setup do
|
810
|
+
TestData.load
|
811
|
+
end
|
812
|
+
|
813
|
+
teardown do
|
814
|
+
TestData.rollback
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
end
|
819
|
+
```
|
820
|
+
|
821
|
+
And then (without any class inheritance complications), simply declare which
|
822
|
+
kind of test you're specifying:
|
823
|
+
|
824
|
+
```ruby
|
825
|
+
class SomeFactoryUsingTest < ActiveSupport::TestCase
|
826
|
+
test_data_mode :factory_bot
|
827
|
+
|
828
|
+
# … tests go here
|
829
|
+
end
|
830
|
+
|
831
|
+
class SomeTestDataUsingTest < ActionDispatch::IntegrationTest
|
832
|
+
test_data_mode :test_data
|
833
|
+
|
834
|
+
# etc.
|
835
|
+
end
|
836
|
+
```
|
837
|
+
|
838
|
+
### Why can't I save multiple database dumps to cover different scenarios?
|
839
|
+
|
840
|
+
For the same reason you (probably) don't have multiple production databases: the
|
841
|
+
fact that Rails apps are monolithic and consolidated is a big reason why they're
|
842
|
+
so productive and comprehensible. This gem is not
|
843
|
+
[VCR](https://github.com/vcr/vcr) for databases. If you were to design separate
|
844
|
+
test data dumps for each feature, stakeholder, or concern, you'd also have more
|
845
|
+
moving parts to maintain, more complexity to communicate, and more pieces that
|
846
|
+
could someday fall into disrepair.
|
847
|
+
|
848
|
+
By having a single `test_data` database that grows up with your application just
|
849
|
+
like `production` does—with both having their schemas and data migrated
|
850
|
+
incrementally over time—your integration tests that depend on `test_data` will
|
851
|
+
have an early opportunity to catch bugs that otherwise wouldn't be found until
|
852
|
+
they were deployed into a long-lived environment like staging or (gasp!)
|
853
|
+
production itself.
|
854
|
+
|
855
|
+
### Are you sure I should commit these SQL dumps? They're way too big!
|
856
|
+
|
857
|
+
If the dump files generated by `test_data:dump` are absolutely massive, consider the cause:
|
858
|
+
|
859
|
+
1. If you inadvertently created more data than necessary, you might consider
|
860
|
+
resetting (or rolling back) your changes and making another attempt at
|
861
|
+
generating a more minimal set of test data
|
862
|
+
|
863
|
+
2. If certain tables have a lot of records but aren't very relevant to your
|
864
|
+
tests (e.g. audit logs), you might consider either of these options:
|
865
|
+
|
866
|
+
* Add those tables to the `config.non_test_data_tables` configuration array,
|
867
|
+
where they'd still be committed to git, but won't loaded by your tests
|
868
|
+
|
869
|
+
* Exclude data from those tables entirely by adding them to the
|
870
|
+
`config.dont_dump_these_tables` array
|
871
|
+
|
872
|
+
3. If the dumps are _necessarily_ really big (some apps are complex!), consider
|
873
|
+
looking into [git-lfs](https://git-lfs.github.com) for tracking them without
|
874
|
+
impacting the size and performance of the git slug. (See [GitHub's
|
875
|
+
documentation](https://docs.github.com/en/github/managing-large-files/working-with-large-files)
|
876
|
+
on what their service supports)
|
877
|
+
|
878
|
+
_[Beyond these options, we'd also be interested in a solution that filtered data
|
879
|
+
in a more granular way than ignoring entire tables. If you have a proposal you'd
|
880
|
+
be interested in implementing, [suggest it in an issue](/issues/new)!]_
|
881
|
+
|
882
|
+
### Tests shouldn't use shared test data, they should instantiate the objects they need!
|
883
|
+
|
884
|
+
Agreed! Nothing is simpler than calling `new` to create an object.
|
885
|
+
|
886
|
+
If it's possible to write a test that looks like this, do it. Don't use shared
|
887
|
+
test data loaded from this gem or any other:
|
888
|
+
|
889
|
+
```ruby
|
890
|
+
def test_exclude_cancelled_orders
|
891
|
+
good_order = Order.new
|
892
|
+
bad_order = Order.new(cancelled: true)
|
893
|
+
user = User.create!(orders: good_order, bad_order)
|
894
|
+
|
895
|
+
result = user.active_orders
|
896
|
+
|
897
|
+
assert_includes good_order
|
898
|
+
refute_includes bad_order
|
899
|
+
end
|
900
|
+
```
|
901
|
+
|
902
|
+
This test is simple, self-contained, clearly denotes
|
903
|
+
[arrange-act-assert](https://github.com/testdouble/contributing-tests/wiki/Arrange-Act-Assert),
|
904
|
+
and (most importantly) will only fail if the functionality stops working.
|
905
|
+
Maximizing the number of tests that can be written expressively and succinctly
|
906
|
+
without the aid of an external helper is a laudable goal that more teams should
|
907
|
+
embrace.
|
908
|
+
|
909
|
+
However, what if the code you're writing doesn't need 3 records in the database,
|
910
|
+
but 30? Writing that much test setup would be painstaking and—despite being
|
911
|
+
fully-encapsulated—hard for readers to understand what's going on. At that
|
912
|
+
point, you have two options:
|
913
|
+
|
914
|
+
1. Critically validate your design: why is it so hard to set up? Does it
|
915
|
+
_really_ require so much persisted data to exercise this behavior? Would a
|
916
|
+
plain old Ruby object that defined a pure function have been feasible? Could
|
917
|
+
a model instance or even a `Struct` be passed to the
|
918
|
+
[subject](https://github.com/testdouble/contributing-tests/wiki/Subject)
|
919
|
+
instead of loading everything from the database? When automated testing is
|
920
|
+
saved for the very end of a feature's development, it can feel too costly to
|
921
|
+
reexamine design decisions like this, but it's valuable feedback all the
|
922
|
+
same. *Easy to test code is easy to use code*
|
923
|
+
|
924
|
+
2. If the complex setup is a necessary reality of the situation that your app
|
925
|
+
needs to handle (and it often will be!), then having _some_ kind of shared
|
926
|
+
source of test data to use as a starting point can be hugely beneficial.
|
927
|
+
That's why `factory_bot` is so popular, why this gem exists, etc.
|
928
|
+
|
929
|
+
As a result, there is no one-size-fits-all approach. Straightforward behavior
|
930
|
+
that can be invoked with a clear, concise test has no reason to be coupled to a
|
931
|
+
shared source of test data. Subtle behavior that requires lots of data to be
|
932
|
+
exercised would see its tests grow unwieldy without something to help populate
|
933
|
+
that data. So both kinds of test clearly have their place.
|
934
|
+
|
935
|
+
But this is a pretty nuanced discussion that can be hard to keep in mind when
|
936
|
+
under deadline pressure or on a large team where building consensus around norms
|
937
|
+
is challenging. As a result, leaving the decision of which type of test to write
|
938
|
+
to spur-of-the-moment judgment is likely to result in inconsistent test design.
|
939
|
+
Instead, you might consider separating these two categories into separate test
|
940
|
+
types or suites.
|
941
|
+
|
942
|
+
For example, it would be completely reasonable to load this gem's test data for
|
943
|
+
integration tests, but not for basic tests of models, like so:
|
944
|
+
|
945
|
+
```ruby
|
946
|
+
class ActionDispatch::IntegrationTest
|
947
|
+
def setup
|
948
|
+
TestData.load
|
949
|
+
end
|
950
|
+
|
951
|
+
def teardown
|
952
|
+
TestData.rollback
|
953
|
+
end
|
954
|
+
end
|
955
|
+
|
956
|
+
class ActiveSupport::TestCase
|
957
|
+
def setup
|
958
|
+
TestData.truncate
|
959
|
+
end
|
960
|
+
|
961
|
+
def teardown
|
962
|
+
TestData.rollback(:after_data_truncate)
|
963
|
+
end
|
964
|
+
end
|
965
|
+
```
|
28
966
|
|
29
|
-
|
967
|
+
In short, this skepticism is generally healthy, and encapsulated tests that
|
968
|
+
forego reliance on shared sources of test data should be maximized. For
|
969
|
+
everything else, there's `test_data`.
|
30
970
|
|
31
|
-
|
971
|
+
## Code of Conduct
|
32
972
|
|
33
|
-
|
973
|
+
This project follows Test Double's [code of
|
974
|
+
conduct](https://testdouble.com/code-of-conduct) for all community interactions,
|
975
|
+
including (but not limited to) one-on-one communications, public posts/comments,
|
976
|
+
code reviews, pull requests, and GitHub issues. If violations occur, Test Double
|
977
|
+
will take any action they deem appropriate for the infraction, up to and
|
978
|
+
including blocking a user from the organization's repositories.
|
34
979
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/searls/test_data.
|