Add docs on how to write tests for using Active Storage [ci skip]

- Introduces `fixture_file_upload`, and adds an example
- Makes the sections on cleaning up files lower level headers (no need for them to be primary sections)
- Updates the suggested service name for testing to match the one used in a new Rails app
- Updates the path to delete files from, to match the path used in a new Rails app
This commit is contained in:
Alex Ghiculescu 2021-02-24 12:29:41 -06:00
parent 3cf6378a5b
commit 4b71583b62
3 changed files with 144 additions and 37 deletions

View File

@ -8,14 +8,14 @@ module ActionDispatch
module FixtureFile
# Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.file_fixture_path, path), type)</tt>:
#
# post :change_avatar, params: { avatar: fixture_file_upload('spongebob.png', 'image/png') }
# post :change_avatar, params: { avatar: fixture_file_upload('david.png', 'image/png') }
#
# Default fixture files location is <tt>test/fixtures/files</tt>.
#
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
# This will not affect other platforms:
#
# post :change_avatar, params: { avatar: fixture_file_upload('spongebob.png', 'image/png', :binary) }
# post :change_avatar, params: { avatar: fixture_file_upload('david.png', 'image/png', :binary) }
def fixture_file_upload(path, mime_type = nil, binary = false)
if self.class.respond_to?(:fixture_path) && self.class.fixture_path &&
!File.exist?(path)

View File

@ -37,7 +37,7 @@ module ActiveStorage
# blob: first_thumbnail_blob
#
# When processed, Active Record will insert database records for each fixture
# entry and will ensure the Action Text relationship is intact.
# entry and will ensure the Active Storage relationship is intact.
class FixtureSet
include ActiveSupport::Testing::FileFixtures
include ActiveRecord::SecureToken

View File

@ -960,10 +960,33 @@ class Uploader {
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).
Discarding Files Stored During System Tests
Testing
-------------------------------------------
System tests clean up test data by rolling back a transaction. Because destroy
Use [`fixture_file_upload`][] to test uploading a file in an integration or controller test.
Rails handles files like any other parameter.
```ruby
class SignupController < ActionDispatch::IntegrationTest
test "can sign up" do
post signup_path, params: {
name: "David",
avatar: fixture_file_upload("david.png", "image/png")
}
user = User.order(:created_at).last
assert user.avatar.attached?
end
end
```
[`fixture_file_upload`]: https://api.rubyonrails.org/classes/ActionDispatch/TestProcess/FixtureFile.html
### Discarding files created during tests
#### System tests
System tests clean up test data by rolling back a transaction. Because `destroy`
is never called on an object, the attached files are never cleaned up. If you
want to clear the files, you can do it in an `after_teardown` callback. Doing it
here ensures that all connections created during the test are complete and
@ -971,16 +994,26 @@ you won't receive an error from Active Storage saying it can't find a file.
```ruby
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
def remove_uploaded_files
FileUtils.rm_rf("#{Rails.root}/storage_test")
end
# ...
def after_teardown
super
remove_uploaded_files
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
# ...
end
```
If you're using [parallel tests][] and the `DiskService`, you should configure each process to use its own
folder for Active Storage. This way, the `teardown` callback will only delete files from the relevant process'
tests.
```ruby
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# ...
parallelize_setup do |i|
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
end
# ...
end
```
@ -988,47 +1021,121 @@ If your system tests verify the deletion of a model with attachments and you're
using Active Job, set your test environment to use the inline queue adapter so
the purge job is executed immediately rather at an unknown time in the future.
You may also want to use a separate service definition for the test environment
so your tests don't delete the files you create during development.
```ruby
# Use inline job processing to make things happen immediately
config.active_job.queue_adapter = :inline
# Separate file storage in the test environment
config.active_storage.service = :local_test
```
Discarding Files Stored During Integration Tests
-------------------------------------------
[parallel tests]: https://guides.rubyonrails.org/testing.html#parallel-testing
#### Integration tests
Similarly to System Tests, files uploaded during Integration Tests will not be
automatically cleaned up. If you want to clear the files, you can do it in an
`after_teardown` callback. Doing it here ensures that all connections created
during the test are complete and you won't receive an error from Active Storage
saying it can't find a file.
`teardown` callback.
```ruby
module RemoveUploadedFiles
class ActionDispatch::IntegrationTest
def after_teardown
super
remove_uploaded_files
end
private
def remove_uploaded_files
FileUtils.rm_rf(Rails.root.join('tmp', 'storage'))
end
end
module ActionDispatch
class IntegrationTest
prepend RemoveUploadedFiles
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
end
```
If you're using [parallel tests][] and the Disk service, you should configure each process to use its own
folder for Active Storage. This way, the `teardown` callback will only delete files from the relevant process'
tests.
```ruby
class ActionDispatch::IntegrationTest
parallelize_setup do |i|
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
end
end
```
[parallel tests]: https://guides.rubyonrails.org/testing.html#parallel-testing
### Adding attachments to fixtures
You can add attachments to your existing [fixtures][]. First, you'll want to create a separate storage service:
```yml
# config/storage.yml
test_fixtures:
service: Disk
root: <%= Rails.root.join("tmp/storage_fixtures") %>
```
This tells Active Storage where to "upload" fixture files to, so it should be a temporary directory. By making it
a different directory to your regular `test` service, you can separate fixture files from files uploaded during a
test.
Next, create fixture files for the Active Storage classes:
```yml
# active_storage/attachments.yml
david_avatar:
name: avatar
record: david (User)
blob: david_avatar_blob
```
```yml
# active_storage/blobs.yml
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>
```
Then put a file in your fixtures directory (the default path is `test/fixtures/files`) with the corresponding filename.
See the [`ActiveStorage::FixtureSet`][] docs for more information.
Once everything is set up, you'll be able to access attachments in your tests:
```ruby
class UserTest < ActiveSupport::TestCase
def test_avatar
avatar = users(:david).avatar
assert avatar.attached?
assert_not_nil avatar.download
assert_equal 1000, avatar.byte_size
end
end
```
#### Cleaning up fixtures
While files uploaded in tests are cleaned up [at the end of each test](#discarding-files-created-during-tests),
you only need to clean up fixture files once: when all your tests complete.
If you're using parallel tests, call `parallelize_teardown`:
```ruby
class ActiveSupport::TestCase
# ...
parallelize_teardown do |i|
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end
# ...
end
```
If you're not running parallel tests, use `Minitest.after_run` or the equivalent for your test
framework (eg. `after(:suite)` for RSpec):
```ruby
# test_helper.rb
Minitest.after_run do
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end
```
[fixtures]: https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures
[`ActiveStorage::FixtureSet`]: https://api.rubyonrails.org/classes/ActiveStorage/FixtureSet.html
Implementing Support for Other Cloud Services
---------------------------------------------