Skip to content

Commit d89d14d

Browse files
authored
Merge pull request rails#41543 from ghiculescu/as-guide-testing
Add docs on how to write tests when using Active Storage
2 parents e45cb30 + 4b71583 commit d89d14d

File tree

3 files changed

+138
-31
lines changed

3 files changed

+138
-31
lines changed

actionpack/lib/action_dispatch/testing/test_process.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ module TestProcess
88
module FixtureFile
99
# Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_fixture_path, path), type)</tt>:
1010
#
11-
# post :change_avatar, params: { avatar: fixture_file_upload('spongebob.png', 'image/png') }
11+
# post :change_avatar, params: { avatar: fixture_file_upload('david.png', 'image/png') }
1212
#
1313
# Default fixture files location is <tt>test/fixtures/files</tt>.
1414
#
1515
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
1616
# This will not affect other platforms:
1717
#
18-
# post :change_avatar, params: { avatar: fixture_file_upload('spongebob.png', 'image/png', :binary) }
18+
# post :change_avatar, params: { avatar: fixture_file_upload('david.png', 'image/png', :binary) }
1919
def fixture_file_upload(path, mime_type = nil, binary = false)
2020
if self.class.respond_to?(:fixture_path) && self.class.fixture_path &&
2121
!File.exist?(path)

activestorage/lib/active_storage/fixture_set.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module ActiveStorage
3737
# blob: first_thumbnail_blob
3838
#
3939
# When processed, Active Record will insert database records for each fixture
40-
# entry and will ensure the Action Text relationship is intact.
40+
# entry and will ensure the Active Storage relationship is intact.
4141
class FixtureSet
4242
include ActiveSupport::Testing::FileFixtures
4343
include ActiveRecord::SecureToken

guides/source/active_storage_overview.md

Lines changed: 135 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -960,75 +960,182 @@ class Uploader {
960960

961961
NOTE: Using [Direct Uploads](#direct-uploads) can sometimes result in a file that uploads, but never attaches to a record. Consider [purging unattached uploads](#purging-unattached-uploads).
962962

963-
Discarding Files Stored During System Tests
963+
Testing
964964
-------------------------------------------
965965

966-
System tests clean up test data by rolling back a transaction. Because destroy
966+
Use [`fixture_file_upload`][] to test uploading a file in an integration or controller test.
967+
Rails handles files like any other parameter.
968+
969+
```ruby
970+
class SignupController < ActionDispatch::IntegrationTest
971+
test "can sign up" do
972+
post signup_path, params: {
973+
name: "David",
974+
avatar: fixture_file_upload("david.png", "image/png")
975+
}
976+
977+
user = User.order(:created_at).last
978+
assert user.avatar.attached?
979+
end
980+
end
981+
```
982+
983+
[`fixture_file_upload`]: https://api.rubyonrails.org/classes/ActionDispatch/TestProcess/FixtureFile.html
984+
985+
### Discarding files created during tests
986+
987+
#### System tests
988+
989+
System tests clean up test data by rolling back a transaction. Because `destroy`
967990
is never called on an object, the attached files are never cleaned up. If you
968991
want to clear the files, you can do it in an `after_teardown` callback. Doing it
969992
here ensures that all connections created during the test are complete and
970993
you won't receive an error from Active Storage saying it can't find a file.
971994

972995
```ruby
973996
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
974-
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
975-
976-
def remove_uploaded_files
977-
FileUtils.rm_rf("#{Rails.root}/storage_test")
978-
end
979-
997+
# ...
980998
def after_teardown
981999
super
982-
remove_uploaded_files
1000+
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
1001+
end
1002+
# ...
1003+
end
1004+
```
1005+
1006+
If you're using [parallel tests][] and the `DiskService`, you should configure each process to use its own
1007+
folder for Active Storage. This way, the `teardown` callback will only delete files from the relevant process'
1008+
tests.
1009+
1010+
```ruby
1011+
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
1012+
# ...
1013+
parallelize_setup do |i|
1014+
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
9831015
end
1016+
# ...
9841017
end
9851018
```
9861019

9871020
If your system tests verify the deletion of a model with attachments and you're
9881021
using Active Job, set your test environment to use the inline queue adapter so
9891022
the purge job is executed immediately rather at an unknown time in the future.
9901023

991-
You may also want to use a separate service definition for the test environment
992-
so your tests don't delete the files you create during development.
993-
9941024
```ruby
9951025
# Use inline job processing to make things happen immediately
9961026
config.active_job.queue_adapter = :inline
997-
998-
# Separate file storage in the test environment
999-
config.active_storage.service = :local_test
10001027
```
10011028

1002-
Discarding Files Stored During Integration Tests
1003-
-------------------------------------------
1029+
[parallel tests]: https://guides.rubyonrails.org/testing.html#parallel-testing
1030+
1031+
#### Integration tests
10041032

10051033
Similarly to System Tests, files uploaded during Integration Tests will not be
10061034
automatically cleaned up. If you want to clear the files, you can do it in an
1007-
`after_teardown` callback. Doing it here ensures that all connections created
1008-
during the test are complete and you won't receive an error from Active Storage
1009-
saying it can't find a file.
1035+
`teardown` callback.
10101036

10111037
```ruby
1012-
module RemoveUploadedFiles
1038+
class ActionDispatch::IntegrationTest
10131039
def after_teardown
10141040
super
1015-
remove_uploaded_files
1041+
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
10161042
end
1043+
end
1044+
```
10171045

1018-
private
1046+
If you're using [parallel tests][] and the Disk service, you should configure each process to use its own
1047+
folder for Active Storage. This way, the `teardown` callback will only delete files from the relevant process'
1048+
tests.
1049+
1050+
```ruby
1051+
class ActionDispatch::IntegrationTest
1052+
parallelize_setup do |i|
1053+
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
1054+
end
1055+
end
1056+
```
1057+
1058+
[parallel tests]: https://guides.rubyonrails.org/testing.html#parallel-testing
1059+
1060+
### Adding attachments to fixtures
1061+
1062+
You can add attachments to your existing [fixtures][]. First, you'll want to create a separate storage service:
1063+
1064+
```yml
1065+
# config/storage.yml
1066+
1067+
test_fixtures:
1068+
service: Disk
1069+
root: <%= Rails.root.join("tmp/storage_fixtures") %>
1070+
```
1071+
1072+
This tells Active Storage where to "upload" fixture files to, so it should be a temporary directory. By making it
1073+
a different directory to your regular `test` service, you can separate fixture files from files uploaded during a
1074+
test.
1075+
1076+
Next, create fixture files for the Active Storage classes:
1077+
1078+
```yml
1079+
# active_storage/attachments.yml
1080+
david_avatar:
1081+
name: avatar
1082+
record: david (User)
1083+
blob: david_avatar_blob
1084+
```
1085+
1086+
```yml
1087+
# active_storage/blobs.yml
1088+
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>
1089+
```
1090+
1091+
Then put a file in your fixtures directory (the default path is `test/fixtures/files`) with the corresponding filename.
1092+
See the [`ActiveStorage::FixtureSet`][] docs for more information.
10191093

1020-
def remove_uploaded_files
1021-
FileUtils.rm_rf(Rails.root.join('tmp', 'storage'))
1094+
Once everything is set up, you'll be able to access attachments in your tests:
1095+
1096+
```ruby
1097+
class UserTest < ActiveSupport::TestCase
1098+
def test_avatar
1099+
avatar = users(:david).avatar
1100+
1101+
assert avatar.attached?
1102+
assert_not_nil avatar.download
1103+
assert_equal 1000, avatar.byte_size
10221104
end
10231105
end
1106+
```
1107+
1108+
#### Cleaning up fixtures
10241109

1025-
module ActionDispatch
1026-
class IntegrationTest
1027-
prepend RemoveUploadedFiles
1110+
While files uploaded in tests are cleaned up [at the end of each test](#discarding-files-created-during-tests),
1111+
you only need to clean up fixture files once: when all your tests complete.
1112+
1113+
If you're using parallel tests, call `parallelize_teardown`:
1114+
1115+
```ruby
1116+
class ActiveSupport::TestCase
1117+
# ...
1118+
parallelize_teardown do |i|
1119+
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
10281120
end
1121+
# ...
1122+
end
1123+
```
1124+
1125+
If you're not running parallel tests, use `Minitest.after_run` or the equivalent for your test
1126+
framework (eg. `after(:suite)` for RSpec):
1127+
1128+
```ruby
1129+
# test_helper.rb
1130+
1131+
Minitest.after_run do
1132+
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
10291133
end
10301134
```
10311135

1136+
[fixtures]: https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures
1137+
[`ActiveStorage::FixtureSet`]: https://api.rubyonrails.org/classes/ActiveStorage/FixtureSet.html
1138+
10321139
Implementing Support for Other Cloud Services
10331140
---------------------------------------------
10341141

0 commit comments

Comments
 (0)