RSpecでDynamoDBのユニットテストを実装

# 注意事項
**現在、[FakeDynamo](https://github.com/ananthakumaran/fake_dynamo) はメンテナンスが終了しており、利用は非推奨です。
DynamoDB のユニットテストを実装される方は、[DynamoDB Local](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html) をご使用ください。**

> This project is no longer maintained. Checkout Amazon
[DynamoDB Local](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html) if you are looking for
implementation of latest version.

> <cite>[ananthakumaran/fake_dynamo](https://github.com/ananthakumaran/fake_dynamo)</cite>

RSpec を使って DynamoDB Local を用いる方法については、

- [RSpecでDynamoDBなどの外部サービス機能のユニットテストを実現する](http://qiita.com/yotaro/items/caff6dc4c6b6ed2b19ee)

などが参考になるかと思います。

## 余談
TravisCI で DynamoDB Local を用いた RSpec によるユニットテストの導入に関わることがあったので、機会があればそちらもまとめてみようと思います。
また、Circle CI への DynamoDB Local の導入も予定しているので、実現したらそちらもまとめようかと思います。

き、気が向いたら！笑

# 概要
FakeDynamo というローカルで実行できる DynamoDB のエミュレータを利用し、DynamoDB を使用しているプログラムのユニットテストを RSPec で実装してみました。

# [Amazon DynamoDB](http://aws.amazon.com/jp/dynamodb/)
> Amazon DynamoDB は、1 桁台のミリ秒単位のレイテンシーを必要とするすべての規模のアプリケーションに対応した高速かつフレキシブルな NoSQL データベースサービスです。

いわゆる NoSQL で、データモデルは Tables, Items, Attributes だそうです。
[こちらのスライド](http://www.slideshare.net/shimy_net/amazon-dynamodb-23315068)にザッと目を通すと良いかと思います。

# [FakeDynamo](https://github.com/ananthakumaran/fake_dynamo)

> local hosted, inmemory Amazon DynamoDB emulator.

ローカルで動作する Amazon DynamoDB のインメモリなエミュレータです。こいつを DynamoDB の代わりに利用します。

# 導入
README の Usage 通りに Gem を導入し、起動後に初期化すれば終了です。

なーんとなくポートが使われていないか調べてから起動しました。

```bash
$ netstat -na | grep 4567
$ fake_dynamo --port 4567
```

そして初期化。

```bash
$ curl -X DELETE http://localhost:4567
```

# 動作確認

DynamoDB へのアクセスに使うモデルは`AWS::Record::HashModel`を使って実装しました。
このクラスの使用方法については、[[Ruby on Rails] AWS::Record::HashModel を使って Amazon DynamoDB のモデルを作る](http://dev.classmethod.jp/server-side/ruby-on-rails-aws-hash-model/)を参考にしてます。

```ruby
class User < AWS::Record::HashModel
  def self.inherited(subclass)
  end
  
  set_shard_name "users"
  string_attr :user_id
  string_attr :email
end
```

そして fake_dynamo を起動します。

```bash
$ fake_dynamo --port 4567
```

動作確認ということで、rails console で実行してみました。

```ruby
$ rails console

# 公式サイトに従い設定
pry(main)> AWS.config(:use_ssl => false,
                      :dynamo_db_endpoint => 'localhost',
                      :dynamo_db_port => 4567,
                      :access_key_id => "xxx",
                      :secret_access_key => "xxx")
=> <AWS::Core::Configuration>

# テーブルの作成
pry(main)> User.create_table 1, 1, :shard_name => 'users'
AWS::DynamoDB::Errors::ValidationException: Validation error detected: KeySchema must be a Array
from /usr/local/lib/ruby/gems/1.9.1/gems/aws-sdk-1.48.1/lib/aws/core/client.rb:375:in `return_or_raise'
```

ダメでした。

エラーメッセージで調べてみると Dynamoid という DynamoDB の ORM の Issue にそれらしいコメントがありました。どうやら、古いバージョンを入れたら解決するとのこと。

原因はどうやら、使っている AWS-SDK が古いこと（1系）のようです。
DynamoDB の API Version 2012-08-10 では、2系の AWS-SDK にしか対応してないためした。

> You should use an older version of fake_dynamo. You should be able to see the version you are using by typing gem list | grep fake_dynamo in terminal.

> To install the right version try gem install fake_dynamo --version 0.1.3.

> [Running tests against 2012-08-10 API fails #108](https://github.com/Veraticus/Dynamoid/issues/108)

仕方がないので古いバージョンを入れて再度動作確認！

```ruby
# 公式サイトに従い設定
pry(main)> AWS.config(:use_ssl => false,
                      :dynamo_db_endpoint => 'localhost',
                      :dynamo_db_port => 4567,
                      :access_key_id => "xxx",
                      :secret_access_key => "xxx")
=> <AWS::Core::Configuration>

# テーブルの作成
pry(main)> User.create_table 1, 1, :shard_name => 'users'
=> <AWS::DynamoDB::Table table_name:pixta_credentials>

# インスタンスの生成
pry(main)> user = User.new(user_id: 1, email: "foobar@sample.com")
=> #<User:0x000000086e6e70
 @_data={"user_id"=>1, "email"=>"foobar@sample.com"},
 @_ignore_changes=false,
 @_orig_values={"user_id"=>nil, "email"=>nil},
 @_shard="users">
 
# データベースへ保存
pry(main)> user.save
=> true

```

動きました！

# RSpec で FakeDynamo を使う
Google 大先生に聞いたところ、[Starting up fake_dynamo automatically with rspec test suite](http://zogovic.com/post/61394092695/starting-up-fake-dynamo-automatically-with-rspec)というステキなサイトを教えていただきました。

こちらを参考に`spec/support`内に以下の`dynamo.rb`を追加しました。

```ruby:spec/support/dynamo.rb
# FakeDynamoを使うための初期設定
AWS.config(:use_ssl => false,
           :dynamo_db_endpoint => 'localhost',
           :dynamo_db_port => 4567,
           :access_key_id => "xxx",
           :secret_access_key => "xxx")

# FakeDynamoをRSpec実行時に実行
RSpec.configure do |config|
  dynamo_thread = nil

  config.before(:suite) do
    FakeDynamo::Storage.db_path = "#{Rails.root}/tmp/db.fdb"
    #FakeDynamo::Logger.setup(:warn) # version 0.1.3 には存在しない
    FakeDynamo::Storage.instance.load_aof

    dynamo_thread = Thread.new do
      $stdout = open("/dev/null", "w") # FakeDynamo の標準出力を無効化
      FakeDynamo::Server.run!(port: 4567, bind: 'localhost') do |server|
        if server.respond_to?('config') && server.config.respond_to?('[]=')
          server.config[:AccessLog] = []
        end
      end
    end
  end

# FakeDynamoをRSpec終了時に終了
  config.after(:suite) do
    FakeDynamo::Storage.instance.shutdown
    dynamo_thread.exit
  end
```

これで、実行時に FakeDynamo が立ち上がるようになります。本当は良くないですが、詳細については理解せずに流用してます。

Spec ファイルでは`before(:all)`を使い、データベースの初期化とテーブルのクリエイトを最初に行うようにしました。

```ruby:spec/model/user_spec.rb
require 'spec_helper'

describe User do
  before(:all) do
    # DBの初期化
    Net::HTTP.start('localhost', 4567).delete('/')
    
    # テーブルの作成
    create_opts = {}
    create_opts[:hash_key] = { User.hash_key => :string }
    AWS::DynamoDB.new.tables.create(User.shard_name, 1, 1, create_opts)
  end

  before do
    @user = User.new(user_id: 1, email "foobar@hoge.com")
  end

  subject { @user }

  it { should respond_to(:user_id) }
  it { should respond_to(:email) }

  it { should be_valid }
end
```

これで DynamoDB を用いたプログラムのユニットテストを RSpec で実装できました。