ActiveRecordを拡張するgemでテストを書くには?

エンジニアの越川です。ActiveRecordを拡張するgemを作りたい。そんなときテストで使うダミーのモデルのテーブルをどう作るかに悩みますね。

本格的なRails拡張ではrails pluginコマンドを用いて開発することが多いと思います。その際は自動で作成されるspec/dummy配下のダミーアプリでmigrationを書くのが楽です。

一方そこまで大きくないActiveRecordの拡張ではもっと手軽に書きたいですね。

例として、protectedカラムがtrueのとき、destroyが出来ないようにするActiveRecordの拡張を考えてみましょう。(今回作成したgemは、github.com/ppworks/kienaideに置いてあります。)

  • gemの作成手順
  • テストの書き方 <-本題はここです
  • rubygemsにリリース

と言った流れを見て行きましょう。

gemの作成手順

gemの作成はbundle gemで行います。

bundle gem kienaide

出来上がったディレクトリ配下を見てみましょう。

cd kienaide/

自動的にgitrepogitoryになっているので状態を確認してみます。

git status

このようなファイルが出来ています。

        new file:   .gitignore
        new file:   Gemfile
        new file:   LICENSE.txt
        new file:   README.md
        new file:   Rakefile
        new file:   kienaide.gemspec
        new file:   lib/kienaide.rb
        new file:   lib/kienaide/version.rb

お作法として、この時点でInitial Commitを作成しておきましょう。

git commit -m "Initial commit"

以降はステップごとに、git commitする想定です。

さて、さっそく今回作るgemの依存gemを記述します。ActiveRecordの拡張なのでactiverecordを追加しました。続いて、開発用にテストを書きたいのでrspecとテスト用のデータベースとしてsqlite3を追加しました。

kienaide.gemspec

   spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
   spec.require_paths = ["lib"]

+  spec.add_dependency "activerecord", ["> 3.0", "< 5.0"]
+  spec.add_development_dependency "rspec", "~> 3.0"
+  spec.add_development_dependency "sqlite3", "~> 1.0"
   spec.add_development_dependency "bundler", "~> 1.7"
   spec.add_development_dependency "rake", "~> 10.0"
 end

次にテスト用のディレクトリを用意します。

mkdir spec

spec_helperは最小限にこんな感じで書いてみました。

spec/spec_helper.rb

require 'bundler/setup'
Bundler.require
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

RSpec.configure do
end

テストの書き方

本題です。テスト用のデータベースの用意を書きます。ポイントはメモリ上にデータベースを用意することと、Railsmigrationの仕組みを利用する事です。

spec/support/setup_database.rb

ActiveRecord::Base.configurations = {'test' => {adapter: 'sqlite3', database: ':memory:'}}
ActiveRecord::Base.establish_connection :test

class CreateAllTables < ActiveRecord::Migration
  def self.up
    create_table(:posts) do |t|
      t.text :content
      t.boolean :protected
    end
  end
end

ActiveRecord::Migration.verbose = false
CreateAllTables.up

メモリ上にDatabaseを用意するところ

{'test' => {adapter: 'sqlite3', database: ':memory:'}}

migrationを実行するところ

ActiveRecord::Migration.verbose = false
CreateAllTables.up

がポイントです。こうしたファイルを用意することで手軽に、 テストで使うダミーのモデルのテーブル を用意することが出来ました。

テスト用のデータベースの用意が出来ましたので、テストを書いていきましょう。

protectedカラムがtrueのとき、destroyが出来ないようにするActiveRecordの拡張を作ります。モデル定義に、kienaideと書いておくと、protectedカラムがtrueの場合、destroyをキャンセルするようにしてみます。

spec/kienaide_spec.rb

require 'spec_helper'

class Post < ActiveRecord::Base
  kienaide
end

RSpec.describe Kienaide do
  let!(:post) { Post.create(content: 'demo', protected: protected) }
  after { Post.delete_all }

  describe '#destroyed?' do
    before { post.destroy }
    subject { post }

    context 'when not protected' do
      let(:protected) { false }
      it { is_expected.to be_destroyed }
    end

    context 'when protected' do
      let(:protected) { true }
      it { is_expected.not_to be_destroyed }
    end
  end
end

テストを書くことで仕様が決まったので実装していきます。

lib/kienaide.rb

require "kienaide/version"
require "active_record"

module Kienaide
  def kienaide
    class_eval do
      before_destroy do
        false if self.protected
      end
    end
  end
end

ActiveRecord::Base.extend Kienaide

テストを通してみましょう。

bundle exec rspec
Kienaide
  #destroyed?
    when not protected
      should be destroyed
    when protected
      should not be destroyed

Finished in 0.01532 seconds (files took 0.37402 seconds to load)
2 examples, 0 failures

無事通りました。

rubygemsにリリース

リリースに備えて、TODOを書き換えます。

kienaide.gemspec

   spec.version       = Kienaide::VERSION
   spec.authors       = ["koshikawa"]
   spec.email         = ["koshikawa@ppworks.jp"]
-  spec.summary       = %q{TODO: Write a short summary. Required.}
-  spec.description   = %q{TODO: Write a longer description. Optional.}
-  spec.homepage      = ""
+  spec.summary       = %q{Protect your record}
+  spec.description   = %q{Protect your record easily}
+  spec.homepage      = "http://github.com/ppworks/kienaide"
   spec.license       = "MIT"

   spec.files         = `git ls-files -z`.split("\x0")

gemのパッケージを作成します。

gem build kienaide.gemspec
  Successfully built RubyGem
  Name: kienaide
  Version: 0.0.1
  File: kienaide-0.0.1.gem

rubygemsに登録します。アカウント。お持ちでない場合は、rubygemsの登録画面から登録しておきます。

リリースは以下のように行います。

gem push kienaide-0.0.1.gem

EmailとPasswordを求められたら登録したものを入力して下さい。

Pushing gem to https://rubygems.org...
Successfully registered gem: kienaide (0.0.1)

これでgemがrubygemsに登録できました。

いかがだったでしょうか、ActiveRecordの拡張gemを書く際のテストの書き方に迷うこともあると思いますが今回のようなアプローチを使うと手軽にテスト用のデータベースを用意できるので開発が加速すると思います。

今回作成したgemは、github.com/ppworks/kienaideに置いてあります。Pull Requestもお待ちしております:)

最後に

マネーフォワードでは積極的にgemを開発してOSSに貢献していきたいエンジニアを募集しています。
みなさまのご応募お待ちしております!

マネーフォワード採用サイト
https://recruit.moneyforward.com/

Wantedly
https://www.wantedly.com/companies/moneyforward

Pocket