6

Given a model that has an ActiveStorage attachment

class MyObject
  has_one_attached :avatar
end

In a dev environment I am able to retrive the avatar as a StringIO object.

obj = MyObject.new( { valid params } )
file = File.open( Rails.root.join( "spec/support/images/test_image.jpg" ))
obj.avatar.attach( io: file, filename: "test_image.jpg" )
obj.save

version = obj.avatar.variant( resize: '200x200>').processed
version_url = Rails.application.routes.url_helpers.url_for( version )
download = open(version_url)
download.class 
=> StringIO

When I attempt to do the same think in a test environment, open(version_url) returns

Errno::ECONNREFUSED Exception: Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000)

Has anyone managed to successfully download activestorage attachments within a test? How should I configure the test environment to achieve this?

My test environment already has

config.active_storage.service = :test
Rails.application.routes.default_url_options = {host: "localhost:3000"}

What have I overlooked?

EDIT

#storage.yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>
Andy Harvey
  • 12,333
  • 17
  • 93
  • 185
  • Could it be this line: `config.active_storage.service = :test` ? I believe `service` is supposed to be one of the services defined in your `storage.yml` file, not the environment you're running in. So `config.active_storage.service = :local` or whatever it's set to in development may fix it. – rparr Sep 14 '18 at 05:56
  • thanks @rparr. i have the following defined in storage.yml. Should this be sufficent? `test: service: Disk root: <%= Rails.root.join("tmp/storage") %>` – Andy Harvey Sep 14 '18 at 06:02
  • Hmm, if that's defined in your `storage.yml` file then maybe that isn't the issue. Could it be possible that you have your development server running at `localhost:3000` **while** running the test? It could be an error from both processes fighting for the same port. – rparr Sep 14 '18 at 06:40
  • I don't think it's the port. At least, changing test.rb `default_url_options` to "localhost:4999" did not resolve the problem. Is there anything else I should try here? – Andy Harvey Sep 14 '18 at 10:31
  • 1
    There's nothing else I could think of trying. But you might want to take a look at the answer by Carlos Ramirez III. – rparr Sep 15 '18 at 03:01

1 Answers1

7

Stored files are accessed through the Rails application server

Active Storage attachments generate URLs that point to the application. The application URL endpoint then redirects to the real file. This decouples the physical location of the file from the URL and provides indirection which is useful for features such as mirroring.

This also means that in order to access a file using its generated URL, a Rails application server must be running...

Server does not run in test environment

The Rails test suite does not start up a server when running the tests. The tests typically don’t need one in order to run.

Errno::ECONNREFUSED Exception: Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000)

This error occurs because the open call tries to request the file at the server location localhost:3000. Because there’s no server running, it fails.

Even if you start up a development server, it will still fail because the Active Storage Attachment and Blob records are stored in the test database, not the development database.

Bypass the app server and get a path directly to the file

In order to get access to a file or variant in your test suite, you need to bypass the application server and get a direct path to the file on disk.

The Active Storage test suite source code shows us how to do this:

blob_or_variant.service.send(:path_for, blob_or_variant.key)

View Source

This will return a file path (on disk) which you can then open using File.open.

Fixing the example above

In the example above, change

download = open(version_url) # BAD: tries to access using HTTP

to

download = File.open( version.service.send(:path_for, version.key) )

Use stubs to avoid network requests in the test suite

If you are testing code which accesses files using HTTP, it’s best practice to stub the network call to avoid it altogether.

There’s some good examples of how to do this in RSpec here:
RSpec how to stub open?

Carlos Ramirez III
  • 7,314
  • 26
  • 33
  • thanks @carlos, this make sense and looks promising. I will give it a try and report back. An initial question - am I right in thinking that `version.service.send.....` is _not_ the best way to do this in non-test environments? (doesn't take advantage of CDN mirroring, etc). I'm thinking about how to integrate this into my code - `download = open(version_url)` is in a class not in a test, so I guess the class would need to run different code depending on ENV – Andy Harvey Sep 15 '18 at 03:21
  • I guess I'm asking, what are the disadvantages of the `version.service....` approach in non-test environment, if any? – Andy Harvey Sep 15 '18 at 03:23
  • 1
    Ah I see. If the code is ultimately going to live in a non-test environment, then you definitely don’t want to use the `version.service.send` approach. That approach is only viable if you need to access a file in the context of a test. If what you’re looking to do is avoid errors in tests that happen to run that portion of the code, then I suggest you stub the `open` method. Does that make sense? – Carlos Ramirez III Sep 15 '18 at 06:53
  • thanks Carlos, makes perfect sense. I'm just surprised that this is so convoluted with ActiveStorage, it seems like a fairly common and simple requirement. – Andy Harvey Sep 15 '18 at 07:20
  • `version.service.send` works perfectly to avoid the TCP connection error, BTW – Andy Harvey Sep 15 '18 at 07:21
  • Glad I could help! It probably makes sense to think of it like any other web request that you might make in your test suite. With that mindset you would just stub it since it’s always best practice to avoid network requests in your tests. If you have any other questions, just let me know! – Carlos Ramirez III Sep 15 '18 at 07:23
  • Good advice! Thanks – Andy Harvey Sep 15 '18 at 07:24
  • actually, your advice RE web requests and stubs is very useful. for completeness, would you mind adding that to your answer? It may help someone else – Andy Harvey Sep 15 '18 at 07:28
  • 1
    Great idea. Done! – Carlos Ramirez III Sep 15 '18 at 07:32