Rails · · Sampo Kuokkanen

新しいRails 8アプリ作成したら、テストはRSpecでやる!

テストを書く

パート1はこちら 新しいRails 8アプリを作ろう!オススメGem ー 合同会社Sampo Development

作成したばかりだと、テストフレームワークはMinitestになっていて、 それをRSpecに置き換えることができます。ほとんどのプロジェクトだと RSpecを使っているかと思いますが、Rails自体はMinitestでテストされていて、 Basecampなど有名なRailsを使っているところもMinitestを使っています。

わかりやすさでいうと、やはりRSpecはわかりやすいと思うので、今回もRSpecを使って テスト書きたいと思います。

echo 'gem "rspec-rails", group: [:development, :test]' >> Gemfile

というコマンドでRSpecをGemfileに追加します。(bundle実行とrails generate rspec:installは必要ですが)

# config/application.rb

config.generators do |g|
  g.test_framework  :rspec, fixture: false
end

application.rbでテストフレームワークをRSpecに指定します。

rails generate scaffold TimeEntry clocked_in:datetime clocked_out:datetime

今回は時間管理したいので、とりあえずTimeEntryを作成します。 これで打刻できるようにしたいです。

まだユーザーモデルなどもないので、簡単に行きましょう。 テストの設定があっているかどうかという確認にもなります。

invoke  active_record
      create    db/migrate/20220108013901_create_time_entries.rb
      create    app/models/time_entry.rb
      invoke    rspec
      create      spec/models/time_entry_spec.rb
      invoke  resource_route
       route    resources :time_entries
      invoke  scaffold_controller
      create    app/controllers/time_entries_controller.rb
      invoke    tailwindcss
      create      app/views/time_entries
      create      app/views/time_entries/index.html.erb
      create      app/views/time_entries/edit.html.erb
      create      app/views/time_entries/show.html.erb
      create      app/views/time_entries/new.html.erb
      create      app/views/time_entries/_form.html.erb
      create      app/views/time_entries/_time_entry.html.erb
      invoke    resource_route
      invoke    rspec
      create      spec/requests/time_entries_spec.rb
      invoke    helper
      create      app/helpers/time_entries_helper.rb
      invoke      rspec
      create        spec/helpers/time_entries_helper_spec.rb

といろんなファイルが作成されました。中身を確認すると、ちゃんとTailwindが使用されていて、 テストもspecフォルダにはいって、ちゃんとRSpecで作成されています。

では、テストは少し作業が残っています。 - あっている値 - まちがっている値

をいれて、走らせられるようにしましょう!

# spec/models/time_entry_spec.rb
let(:valid_attributes) {
  {
    clocked_in: Time.now,
  }
}

let(:invalid_attributes) {
  {
    clocked_in: Time.now - 1.year,
  }
}

のように変更して、上は正しい値をいれて、下は間違っている値を入れます。 テストはRailsが作成してくれたままにしましょう。

bin/rails db:migrate RAILS_ENV=test

でDBセットアップして、rspecコマンド実行します。

/time_entries POST /create with invalid parameters does not create a new TimeEntry

というエラーが出ましたね。 一年前の時間も打刻できてしまっています。 バリデーション追加しないといけないですね。

Rails 7で追加されたComparisonValidatorを使えば、 Gemを足さなくても日付の比較バリデーションが書けます。

# app/models/time_entry.rb
class TimeEntry < ApplicationRecord
  validates :clocked_in, comparison: { greater_than_or_equal_to: -> { Time.zone.now - 5.minutes } }
end

エラーの場合はTurboの都合で422、unprocessable entityを返すように変わっているので、 それでテストが落ちる場合は以下のように書き換えます。

# spec/requests/time_entries_spec.rb # patch
expect(response).to have_http_status(:unprocessable_content)

これでテストが全部通るようになりました! * 地味にclocked_inの値の追加は何箇所かでやらないといけなかったですが。

次回は翻訳の追加ですね。

2026年追記:MinitestとFixturesもオススメ

この記事を書いた時はRSpecを推していましたが、最近はMinitestとFixturesの組み合わせも 非常にオススメです。

RSpecは表現力が高いですが、DSLが多くて覚えることが多いです。 MinitestはRuby標準ライブラリに含まれていて、追加のGemが不要で、 普通のRubyのコードでテストが書けるのがシンプルでいいところです。

また、テストデータについてはFactoryBotよりもRails標準のFixturesが 実はかなり使いやすいです。Fixturesのメリットは:

  • 速い — Fixturesはテスト開始時にまとめてDBに入るので、テストごとにレコードを作成するFactoryBotより高速
  • Rails標準 — 追加のGemが不要で、Railsのアップデートにも影響されにくい
  • シンプル — YAMLファイルでテストデータを定義するだけ
# test/fixtures/time_entries.yml
morning_entry:
  clocked_in: <%= Time.current %>
  clocked_out: <%= Time.current + 8.hours %>

late_entry:
  clocked_in: <%= Time.current - 1.year %>
# test/models/time_entry_test.rb
class TimeEntryTest < ActiveSupport::TestCase
  test "valid time entry" do
    entry = time_entries(:morning_entry)
    assert entry.valid?
  end

  test "rejects old clocked_in time" do
    entry = time_entries(:late_entry)
    assert_not entry.valid?
  end
end

RSpecに慣れている方も、一度Minitest + Fixturesを試してみてください。 特に新しいプロジェクトではセットアップがほぼゼロで始められるので、手軽です。