S3にaws-sdkを使ってテキストを保存する(RSpec)

## やりたいこと
Rails から S3 にテキストを保存するようにしたので、そのテストを書く
この <a href='https://gist.github.com/cesarandreu/d7fa6dc4cb4f8297955f' target='_blank'>Cesar Andreu さんの github</a> を参考にコード書いてます。というかこれ。

保存方法は前回書いたのでこちらから →<a href='http://qiita.com/hirotakasasaki/items/ed8b24ef228747de1ef7' target='_blank'>「Rails から S3 にテキストファイルを保存する」</a>

## 必要なもの、準備
### 必要なもの
gem
* aws-sdk(バージョンは2.0)
* fakes3

### fakes3
fakes3 は S3 のように振る舞うダミーサーバーを提供してくれる(WEBrick)。
テストを通すたびに実際の S3 に保存されるのはウザいので、
今回は fakes3 を使ってスペックを書く。

### 準備
fakes3 はテストでしか使わないので Gemfile のテスト部分に書いて bundle install。

```
gem 'aws-sdk'

group :test do
  ︙
  gem 'fakes3'
end
```

## S3 の設定
S3と通信するためにアクセスキーやらシークレットキーやらの設定が必要になる。
<a href='http://qiita.com/hirotakasasaki/items/ed8b24ef228747de1ef7#%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB' target='_blank'>前回</a>書いた設定をテスト部分も含めて書き直す。

```config/initializers/aws.rb
if Rails.env.test?
  Aws.config.update({
    credentials: Aws::Credentials.new('access-key', 'secret-key'),
    region: "ap-northeast-1",
    endpoint: 'http://localhost:10001/',
    force_path_style: true
  })
else
  Aws.config.update({
    credentials: Aws::Credentials.new('AWS　のアクセスキー', 'AWS のシークレットキー'),
    region: "ap-northeast-1"
  })
end
```
テストのときとそれ以外で通信する S3 を変更する。
テスト側では endpoint を指定して force_path_style を true にする。
この辺に関しては<a href='http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#initialize-instance_method' target='_blank'>aws-sdk のドキュメント</a>に載ってる。

## fakes3 の設定
続いて fakes3 の設定は spec/support 以下に書いていく。

```spec/support/fakes3.rb
require 'fakes3'

RSpec.configure do |config|
  config.before(:suite) { FakeS3Server.up }
  config.after(:suite) { FakeS3Server.down }
end

class FakeS3Server
  def initialize(pid)
    @pid = pid
  end

  def self.up
    dir_path = Rails.root.join('tmp/fakes3')
    Dir.mkdir(dir_path) unless Dir.exist?(dir_path)
    pid = spawn("bundle exec fakes3 --port 10001 --root #{dir_path}")
    @@instance = FakeS3Server.new(pid)
    return @@instance
  end

  def self.down
    @@instance.down if defined? @@instance
  end

  def down
    if @pid
      Process.kill("SIGINT", @pid)
      Process.waitpid2(@pid)
      @pid = nil
    end
  end
end
```
クラスメソッドの up でサーバーを起動させ、 down で終了させる。
それぞれのタイミングは suite でテスト全体の最初と最後。

fakes3 は

```
fakes3 --port ポート番号 --root /fakes3のパス
```
でサーバーが起動するので、 spawn でコマンドを実行している。
また、 root で指定したパスが S3　のルートパスとして扱われる＆テスト時にしか使用しないので
tmp 配下にルートは設定している。
(ルートもパスも他で使ってなければ何でも大丈夫かと)

## 実際のスペック(spec/requests/)
```spec/requests/aws_sdk_spec.rb
require 'rails_helper'
require 'aws-sdk'

describe Aws::S3 do
  let(:s3_client) { Aws::S3::Client.new }
  let(:s3) { Aws::S3::Resource.new(client: s3_client) }
  let(:bucket) { s3.bucket('sample_bucket') }
  let(:object) { bucket.object('uploads/texts/sample.txt') }

  specify 'S3内にオブジェクトを作成できること' do
    get save_text_s3_url
    expect(response.status).to eq 200

    page = object.get.body.read
    expect(page).to include 'このテキストが保存されます'
  end
end
```
(get で使用しているコントローラーとアクションは<a href='http://qiita.com/hirotakasasaki/items/ed8b24ef228747de1ef7#%E4%BF%9D%E5%AD%98' target='_blank'>保存方法の記事</a>のコントローラー)

Aws::S3::Client と Aws::S3::Resource を使ってキーが "uploads/texts/sample.txt" の body を読み込み、意図した文字列が含まれているかをテストしている。

## まとめ・課題
そんなこんなで一通りテストが出来上がりました。
が、いくつか問題があった。。
* 時々スペックが通らない(サーバーが立ち上がるのに時間がかかるのか時々通らなくなる。spec 配下全てをテストする時は問題ない)
* circleci だと通らない(これがわからない。circleci 側でポートを立てるときは設定が必要なのかも)

## 参考
* <a href='https://github.com/jubos/fake-s3' target='_blank'>fakes3 の github</a>
* <a href='https://gist.github.com/cesarandreu/d7fa6dc4cb4f8297955f' target='_blank'>https://gist.github.com/cesarandreu/d7fa6dc4cb4f8297955f</a>
* <a href='http://www.rubydoc.info/gems/fakes3/0.2.2/FakeS3' target='_blank'>ドキュメント</a>