LoginSignup
3
5

More than 5 years have passed since last update.

CarrierWave+Rspec で Fog.mock!/Fog.unmock! の結果が反映されない原因と対処法

Last updated at Posted at 2016-06-15

TL;DR

CarrierWaveを使っていて、普段は Fog.mock! しておきたいけれど、一部のテストケースでは Fog.unmock! したいなら、以下のように書く

rails_helper.rb
RSpec.configure do |config|
  config.before do
    Fog.mock!
    Fog::Mock.reset
    # ↓ Fog.unmock! しても期待通りに動かなくなるのを少し強引に調整
    CarrierWave::Storage::Fog.connection_cache.clear
    # ↓ ここは必要に応じて。 https://blog.engineyard.com/2011/mocking-fog-when-using-it-with-carrierwave を参考に。
    Fog.credentials_path = Rails.root.join('config/test_fog_credentials.yml')
    connection = Fog::Storage.new(:provider => 'AWS')
    connection.directories.create(:key => ENV['AWS_S3_BUCKET'])
  end
end
hoge_spec.rb
require 'rails_helper'
RSpec.describe Hoge, type: :model do
  before do
    # 実際にS3を叩きたい時はbeforeでunmockする
    Fog.unmock!
  end
end

原理とか説明的なの

ざっとした解説ですが、読みたい人はどうぞ。

なぜ Fog.unmock! しただけでは動かないのか

以下のFog公式のIssueにあるとおり、Fog.mock!/Fog.unmock!はFog::Storage以下のクラスをイニシャライズするときにMockするかどうかを変更するだけで、既存のインスタンスには影響を及ぼさないようです。

CarrierWaveでは、かなり根っこの方でそのインスタンスを保持しているようでした。

なぜ上記のコードで動くようになるのか

ようするに、インスタンスを保持しているのが原因なので、それを一度削除し再生成させることで解決します。

重要なのは CarrierWave::Storage::Fog.connection_cache.clear の部分です。

carrierwave-0.10.0/lib/carrierwave/storage/fog.rb の 102行目あたりに以下のようなコードがあります

fog.rb
def connection
  @connection ||= begin
    options = credentials = uploader.fog_credentials
    self.class.connection_cache[credentials] ||= ::Fog::Storage.new(options)
  end
end

この中の self.class.connection_cache[credentials] が既に存在していると、新しくイニシャライズされなくなってしまうので、一度このインスタンス変数をclearすることで、次に呼び出されたときも ::Fog::Storage.new(options) が呼び出されるようにしているわけです。

もちろん、 before(:each) でやらずに before(:all) でやるなど、インスタンス生成コストを下げる方法はあるのですが、インスタンス生成のコストがかなり小さく無視出来るようなものだったので、最も単純な例をこの記事では挙げています。(実運用上もこの方法にしていますが、十分高速でした)

mockする前とmockした後の速度比較

非常に極端な例ですが、実際の例です。
ごく一部、aws-sdkでS3のAPIを直接叩いている部分があるプロダクトで、その部分のテストは実際にS3と通信しないと本当に動くことの確認が出来ないというものがありました。
この記事の方法をみつけるまでは、色んなことを諦めて全てのテストケースでS3と通信していました。
FactoryGirl側で、ユーザー情報やユーザーによる投稿の情報に画像が入っている(必須な場合もある)ので、ほぼ全てのケースで無意味にS3へ画像をアップロードしていました。

テストケースの数は399、RSpecの実行時間だけで38分程度かかっていたものが、2分半程度に短縮されました。明確にここがボトルネックだったんですね。ここまで寄与するとは…。

3
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5