暇人じゃない

Rails 6.1 + Active Storage + factory_bot で undefined method `file_fixture_path' エラーが発生した
Rails

Active Storage で画像のアップロードを実装しているサービスで、factory_bot で画像付きのテストデータを生成するために以下のような factory を定義していた。

include ActionDispatch::TestProcess

factory :foo_image do
  image { fixture_file_upload("spec/fixtures/foo.jpg") }
end

このサービスのアプリケーションを Rails 6.0.x から Rails 6.1.x (Rails 6.1.3.1) にアップデートしたところ、 以下のエラーが発生するようになった。

Failure/Error: image { fixture_file_upload("spec/fixtures/foo.jpg") }

NoMethodError:
  undefined method `file_fixture_path' for #<Class:0x00007ff5e40a8278>
  Did you mean?  fixture_file_upload

対応方法

そもそも fixture_file_upload を提供する ActionDispatch::TestProcessinclude せずに Rack::Test::UploadedFile を直接呼び出す方式に書き換えた。

factory :foo_image do
  image { Rack::Test::UploadedFile.new("spec/fixtures/foo.jpg", "image/jpg") }
end

fixture_file_upload を使わなくなった理由は、ActionDispatch::TestProcessinclude すると、 session メソッドや cookies メソッドといった、テストデータの生成に関係のないメソッドも include されることが分かったから。

fixture_file_upload がやっていることは渡されたパスを良い感じに解決して Rack::Test::UploadedFile を呼んでいるだけなので、簡単に置き換えることができる。

この件については 2018 年頃に factory_bot の Issue で言及されていた。

なので、余計なメソッドが include されるのは Rails 6.1.x だけではなく、以前のバージョンからあった。

余談: ActiveSupport::Testing::FileFixturesinclude する方法を採用しなかった理由

今回のエラーはそもそも fixture_file_upload から参照する file_fixture_path が見つからないというのが原因。

インターネットを検索すると ActiveSupport::Testing::FileFixturesinclude する方法がヒットする。 file_fixture_path というクラス変数が生成されるのでエラーが発生しなくなるという話。

これはこれで良いと思うが、2 つの module を include することにモヤモヤ感があり、後でハマりそうな気がしたので採用しなかった。

敢えてこれを採用するメリットを考えてみたが、file_fixture_path をカスタマイズすることで、ファイルパスを短く書ける、とかだろうか。

# rails_helper.rb
FactoryBot::SyntaxRunner.class_eval do
  include ActionDispatch::TestProcess
  include ActiveSupport::Testing::FileFixtures

  self.file_fixture_path = "spec/fixtures"
end

# foo_images.rb
factory :foo_image do
  image { fixture_file_upload("foo.jpg") }
end

About

chocoby (GitHub / Twitter)

個人事業主のソフトウェア開発者です。 Ruby と Rails を使った Web サービスの開発を得意としています。

CurryBu というサービスや、jp_prefecture という Gem を作っています。