LoginSignup
11
7

More than 5 years have passed since last update.

fakes3とglintでS3を使った実装のテストを行う

Posted at

Amazon S3へのファイルアップロードを使った実装のテストの際に利用したのでメモ。

Fake S3はAmazon s3をシュミレートしてくれるサーバで、glintは空いているポートを見つけてサーバを立ち上げてくれる。

参考ページ

参考にさせてもらったページ。

基本的にはほとんど同じですが、glintを使った処理のconfig.after :suiteのところで時々エラーが出てしまったので、多少改変して利用しました。

前提条件

とりあえず利用している環境情報

  • Rails 4.2.7
  • Rspec 3系

インストール

テストのときだけFake S3を起動したいので、Gemfileのgroup :testに追加します。

Gemfile
gem 'aws-sdk', '~> 2'

group :test do
  gem 'fakes3'
  gem 'glint'
end

そして、bundle installを実行。

使う

AWS S3への通常の接続設定

Herokuのドキュメント Direct to S3 Image Uploads in Rails を参考に、config/initializers/aws.rbに定義していました。
基本的には環境変数に定義するようにしてます。

config/initializers/aws.rb
Aws.config.update({
  region: 'ap-northeast-1',
  credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])

Fake S3の起動

Rspecのconfig.before :suiteのタイミングで起動します。(つまりテスト実行時に1度だけ実行する)
サーバが止まったタイミングで一時フォルダに作られたファイルを削除するようにしています。(server.on_stopped
参考URLでは、config.after :suiteでファイル削除をしていて、それでも動くと思っていたのですが、なぜかFake S3が起動し終わる前にconfig.after :suiteブロックが評価されてしまい、一時フォルダが見つからないというエラーで落ちたり落ちなかったりと不安定だったので、やめました。

spec/support/fakes3.rb
require 'tmpdir'
require 'glint'

RSpec.configure do |config|
  config.before :suite do
    rootdir = Dir.mktmpdir
    server = Glint::Server.new(nil, signals: [:INT]) do |port|
      exec "bundle exec fakes3 -p #{port} -r #{rootdir}", err: '/dev/null'
    end
    # サーバが止まったタイミングでファイルを消す
    server.on_stopped = -> {
      if Dir.exists? Glint::Server.info[:fakes3][:root]
        FileUtils.remove_entry_secure(Glint::Server.info[:fakes3][:root])
      end
    }
    server.start

    Glint::Server.info[:fakes3] = {
        address: "127.0.0.1:#{server.port}",
        root: rootdir
    }
  end
end

Fake S3の起動情報をGlint::Server.infoというグローバル領域に保存しておきます。

AWS SDKを使ってFake S3へ接続

接続はエンドポイントをローカルにする必要があります。接続はconfig/initializers/aws.rbで、S3_BUCKETという定数を作っているので、それをスタブで置き換えます。

rails_helper.rb
RSpec.configure do |config|
  # 他の設定は省略
  # ...
  config.before :each do |example|
    # S3_BUCKETをfakes3に接続したスタブに置き換える
    s3_client = Aws::S3::Client.new(
        access_key_id: "dummy_aws_access_key_id",
        secret_access_key: "dummy_aws_secret_access_key",
        region: "ap-northeast-1",
        endpoint: "http://#{Glint::Server.info[:fakes3][:address]}/",
        force_path_style: true)
    Aws::S3::Resource.new(client: s3_client).create_bucket(
        bucket: "dummy_bucket_name"
    )
    stub_const("S3_BUCKET", Aws::S3::Resource.new(client: s3_client).bucket("dummy_bucket_name"))
  end
  # 他の設定は省略
  # ...
end

テストを書いてみる

とりあえず適当にテストを書いてみます。

spec/models/foo.rb
# == Schema Information
#
# Table name: foos
#
#  id                   :integer          not null, primary key
#  s3_object_key        :text
#  created_at           :datetime
#  updated_at           :datetime
#

describe Foo, type: :model do
  let(:s3_object) {
    S3_BUCKET.put_object(
      acl: 'private',
      key: 'spec/files/test.png',
      body: File.open('spec/files/test.png')
    )
  }

  subject { Foo.new(s3_object_key: s3_object.key) }

  it '保存できること' do
    expect(subject).to be_valid
    expect(subject.save).to be_truty
    expect(S3_BUCKET.object(subject.s3_object_key).presigned_url(:get, expires_in: 300)).not_to be_blank
  end
end

感想

テストのときだけFake S3を自動起動し、テストが終了したらファイルを自動削除してくれるようになったのでよかったです。また、S3_BUCKETを毎回スタブで置き換えているので、本物のS3につながることもないという安心感があります。

ただ、全然関係ないテストのときにもS3_BUCKETをスタブに置き換える処理が挟まれるから、微妙にオーバーヘッドあるかなーという気はしてます…。

11
7
0

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
11
7