ActiveStorageでRSpecのtips

November 27, 2021

最近ちゃんとRSpecを書いていないなぁと思うことが多く、ついつい後回しにしがちなテスト。 TDDを意識して書いていきたいところですが、今回はファイルのアップロードまわりで知見がたまってきたのでまとめて公開します。

ほぼ空のビデオファイルを作成する

自作のRailsアプリケーションで動画ファイルのアップロード機能を実装するときSample Videosで公開されているファイルも最低1MBからなのでGitにコミットすることがはばかれます。 そこでFFmpegを使ってほぼ空のビデオファイルを作成できます。

ffmpeg -f lavfi -i color=size=10x10:rate=25:color=black -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -t 1 output.mp4

ちなみにこのときのファイルサイズは3.6 kBでした。

$ stat output.mp4 | grep Size:
  Size: 3555      	Blocks: 8          IO Block: 4096   regular file

https://stackoverflow.com/a/46370114

ほぼ空の画像を作成する

画像ファイル程度ならGitのコミット特に影響はないと思いますが、念の為。

convert -size 1x1 xc:white white.png

このときのファイルサイズは258 bytesでした。

$ stat white.png | grep Size:
  Size: 258       	Blocks: 8          IO Block: 4096   regular file

https://stackoverflow.com/a/39504523

空のバイナリを作成する

上記の方法を調べていた後に判明したのですが、Railsのfixture_file_uploadは上記のような方法を使わなくても影響がなさそうなので、空のテキストファイルはtouchなどでも作れますが、空のバイナリファイルを作る方法も調べておきます。

dd if=/dev/zero of=binary.dat bs=1c count=1

https://stackoverflow.com/a/8831573

FactoryGirl(FactoryBot)でfile_fixtureを使えるようにする

FactoryBot.define do
  factory :article do
    title { 'test' }
    image { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/dummy.jpg'), 'image/png') } # too bad!
  end
end

今回の本題なのですが、上記のダミーファイルを使ってfactoryを定義しようとすると動きはするのですがやたら長ったらしくて見苦しいです。 本来RSpecにはfile_fixtureというメソッドが定義されているため理想としては:

FactoryBot.define do
  factory :article do
    title { 'test' }
    image { file_fixture('dummy.png', 'image/png') } # neat!
  end
end

ただしfactoryにはfile_fixtureが定義されていないためこのままだと当然NoMethodErrorが返ってきてしまいます。

https://github.com/thoughtbot/factory_bot/issues/1282#issuecomment-659707488

ありがたいことに作りかけのPRではあるものの、コードが共有されていたのでこちらを組み込んでみましょう。

module FactoryBot
  class << self
    def file_fixture_path
      Rails.root.join("spec", "fixtures")
    end
  end

  class Evaluator
    def file_fixture(filename, mime_type = nil, binary = false)
      if FactoryBot.file_fixture_path.present?
        path = Pathname.new(File.join(FactoryBot.file_fixture_path, filename))

        if path.exist?
          Rack::Test::UploadedFile.new(path, mime_type, binary)
        else
          msg = "the directory '%s' does not contain a file named '%s'"
          Kernel.raise ArgumentError, msg % [file_fixture_path, filename]
        end
      else
        Kernel.raise "to use the file_fixture helper you must set FactoryBot.file_fixture_path='path/to/fixture_files'"
      end
    end
  end
end

これで無事テストが動くようになりました。


Profile picture

Personal blog by Seiichi Yonezawa.