【Ruby on Rails】RSpec（rspec-rails）を使ってみよう

## まえがき

本記事は、**[DMM WEBCAMP Advent Calendar 2023](https://qiita.com/advent-calendar/2023/infratop)** 8日目記事です。

Ruby on Railsを中心に、DWCメンター・卒業生が記事を投稿しておりますので、是非他の記事もご確認ください！

## はじめに

こんにちは、DMM WEBCAMP メンターの @ukwhatn です。

本カレンダー4回目の担当日となりますが、今日はRuby on RailsにおけるRSpecの使い方について説明していきます。


## RSpecとは？

RSpecは、Ruby 向けの BDD（Behavior-Driven Development=振る舞い駆動開発）フレームワークです。

主にRubyで書かれたアプリケーションの挙動・機能をテストするために利用され、人間が読みやすい形式でのテストコード記述によって「テストからアプリケーションの振る舞いがわかる」というメリットも持っています。

詳しいことは他の記事におまかせしますが、要は「テストを簡単にかけるいい感じのフレームワーク」だと思っていただければ良いと思います。

## RailsアプリでRSpecを使ってみる
:::note info
ここからは、Rails6.1〜7.xのRailsアプリを対象とした記述となります。
主にGemのバージョンなどが異なりますので、適宜読み替えてください。
:::


### 1. Gemfile に追記する

RailsアプリのGemfileに以下の記述を追記してください。
```rb
group :development, :test do
  gem 'rspec-rails', '~> 6.1.0'
  gem 'factory_bot_rails'
  gem 'faker'
end
```

`rspec-rails`はRSpecをRailsアプリケーション内で利用するためのアダプタです。
ここのバージョンがRailsのバージョンによって異なるので、[公式のREADME](https://github.com/rspec/rspec-rails)を確認してください。

また、`factory_bot_rails`はテストデータ（ActiveRecordモデル）のセットアップをサポートしてくれるライブラリ、`faker`はテストデータのダミー値を生成するときに便利なライブラリです。
`rspec-rails`と一緒に利用されることが多いGemなので、これも抑えておきましょう。

それぞれの使い方はこの後説明していきます。


### 2. bundle install

bundle installでGemをインストールします
```sh
bundle install
```

### 3. RSpecをセットアップする

以下のコマンドを使って、RSpecの初期セットアップを実行します。

```
rails generate rspec:install
```

これにより、以下のファイルが生成されます。
```
$ rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb
```

ここから主に使っていくのは`spec`フォルダの中身になるので、ここが作成できているか確認しておきましょう。

### 4. RSpec用のモデルを追記する

RSpecでテストしたいモデルのテストファイルを生成します。
```
rails g rspec:model book
```
`rails g model`コマンドとほぼ同じ記法で大丈夫です。

これで`spec/models/books_spec.rb`と`spec/factories/books.rb`ができていればOKです。（前者は`rails g model`のときに既に作成されているかもしれません）

### 5. FactoryBotの設定を行う

ここで、先程Gemfileに記載したFactoryBotとFakerを使っていきます。

今回は、無難にtitle, authorのカラムがあることにしましょう。
```rb
FactoryBot.define do
  factory :user do
  
    # --- ここから記述していきます ---

    # 本のタイトルや著者名がランダムに生成されます
    title { Faker::Book.title }
    author { Faker::Book.author }
    
    # --- ここまで記述します ---
    
  end
end
```

これで、`Factorybot.build(:user)`でランダムに生成された値が投入されたuserモデルのインスタンスが作成されるようになりました。

このような`Book`に特化した生成メソッドなどが豊富にあるのがFakerのいいところです。
他にも`Faker::Internet.email`でメールアドレス、`Faker::Internet.password`でパスワードなどが生成できます。

詳しくは[公式docs](https://www.rubydoc.info/gems/faker/)を確認してください。

試しに`rails c`で見てみると、
```sh
$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001> FactoryBot.create(:book)
  TRANSACTION (0.0ms)  begin transaction
  Book Create (0.2ms)  INSERT INTO "books" ("title", "author", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "The Last Temptation"], ["author", "Lyman Botsford"], ["created_at", "2023-12-07 14:35:42.760574"], ["updated_at", "2023-12-07 14:35:42.760574"]]
  TRANSACTION (0.2ms)  commit transaction
=> 
#<Book:0x0000000109947fa0
 id: 1,
 title: "The Last Temptation",
 author: "Lyman Botsford",
 created_at: Thu, 07 Dec 2023 14:35:42.760574000 UTC +00:00,
 updated_at: Thu, 07 Dec 2023 14:35:42.760574000 UTC +00:00>
```

良い感じですね。

### 6. テストコードを書く

それでは、このBookモデルに対するテストコードを書いていきます。

`spec/models/spec_book.rb`を開き、
```rb
pending "add some examples to (or delete) #{__FILE__}"
```
を削除します。

それから、以下のように記述していきましょう。

```rb
require 'rails_helper'

RSpec.describe Book, type: :model do
  # ここからテストスタート
  # 作成に対するテスト
  describe 'create' do
    # FactoryBotを使ってbookを作成
    let(:book) { FactoryBot.build(:book) }

    # まずは正しいデータが作成されるかをテスト
    context 'with valid attributes' do
      it 'is valid with a title and author' do
        expect(book).to be_valid
      end
    end

    # 異常系のテスト
    context 'with invalid attributes' do
      # titleがない場合のテスト
      it 'is invalid without a title' do
        book.title = nil
        expect(book).to be_invalid
      end

      # authorがない場合のテスト
      it 'is invalid without a author' do
        book.author = nil
        expect(book).to be_invalid
      end

      # titleが31文字以上の場合のテスト
      it 'is invalid with a title that has more than 31 characters' do
        book.title = 'a' * 31
        expect(book).to be_invalid
      end

      # authorが31文字以上の場合のテスト
      it 'is invalid with a author that has more than 31 characters' do
        book.author = 'a' * 31
        expect(book).to be_invalid
      end
    end
  end
end

```

内容はだいたいコメントの通りです。
最初に作ったbookに対し、それぞれ値を入れ替えてバリデーションに通るかを試していきます。

なお、`book`の内容は各テストで独立しているので、`authorがない場合のテスト`のときに`title`まで`nil`になっているわけではありません。

出力は
```
Book
  create
    with valid attributes
      is valid with a title and author
    with invalid attributes
      is invalid without a title
      is invalid without a author
      is invalid with a title that has more than 31 characters
      is invalid with a author that has more than 31 characters

Finished in 0.10826 seconds (files took 0.61249 seconds to load)
5 examples, 0 failures

```
のようになるはずです。

テストは正常系・異常系などのカテゴリごとに`context`を使って区切ると良いでしょう。

### 7. あとは色々書いていく

あとは必要に応じて色々書いていけば良いです。

[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612) などはかなり参考になりますので、目を通してみてください。

また、モデルだけでなくメソッドやアクションに対するテストもできます。
[JunichiIto/everydayrails-rspec-jp-2022](https://github.com/JunichiIto/everydayrails-rspec-jp-2022)などはサンプルとして優秀ですので、是非specフォルダを覗いてみてください。

---

如何でしたでしょうか。

普段あまりテストを書かないという方は、バリデーションがうまく働いているかを確認するところからRSpecに入門してみませんか？