6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

自作アプリのUserモデルにテストコード(RSpec)を書いてみた

Last updated at Posted at 2019-06-24

はじめに

Railsでよく見るバリデーションのテストをRSpecで書きました。

テスト内容

今回は、name, user_id, email を持つUserモデルを作成しました。

マイグレーションファイル
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name
      t.string :user_id
      t.string :email

      t.timestamps
    end
  end
end

Userモデルのテストでは、主にバリデーションのテストを行いました。

app/models/user.rb
class User < ApplicationRecord
  before_save { email.downcase! }
  validates :name, presence: true,
                   length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true,
                    length: { maximum: 255 },
                    uniqueness: { case_sensitive: false },
                    format: { with: VALID_EMAIL_REGEX }
end

バリデーションの設定に関しては、Railsチュートリアルを参考にしています。バリデーションの設定や意味などは、そちらを参考に。

環境設定

Gemfile に以下のように追記します。

group :development, :test do
  # 省略
  gem 'factory_bot_rails'
  gem 'rspec-rails'
  # 省略
end

そうすると、Userモデル作成と同時にRSpecのファイルが生成されます。

$ rails g model User ...

Running via Spring preloader in process 19654
      invoke  active_record
      create    db/migrate/20190617xxxxxx_create_userss.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

なので、テストは以下に書き込めばいいと。

spec/models/user_spec.rb

テスト作成

以下が、Userモデルに対するすテスト(RSpec)

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  before do
    @user = User.create(
      name:    "Yamada Taro",
      user_id: "taro",
      email:   "taro@example.com",
    )
  end

  # name、user_id、emailがあれば有効な状態であること
  it "is valid with a name, user_id, email" do
    user = User.new(
      name:    "Sato Taro",
      user_id: "sato",
      email:   "sato@example.com",
    )
    expect(user).to be_valid
  end

  # nameがなければ無効な状態であること(nameは必須項目)
  it "is invalid without a name" do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include("can't be blank")
  end

  # メールアドレスがなければ無効な状態であること(emailは必須項目)
  it "is invalid without an email address" do
    user = User.new(email: nil)
    user.valid?
    expect(user.errors[:email]).to include("can't be blank")
  end

  # 重複したメールアドレスなら無効な状態であること
  it "is invalid with a duplicate email address" do
    user = User.new(email: "taro@example.com")
    user.valid?
    expect(user.errors[:email]).to include("has already been taken")
  end

  # メールアドレスが小文字化されていること
  it "email addresses should be saved as lower-case" do
    mixed_case_email = "TANAKA@example.com"
    @user.email = mixed_case_email
    @user.save
    expect(@user[:email]).to eq("tanaka@example.com")
  end

  # メールアドレスの一意性の検証
  it "email is unique" do
    user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
    expect(user).to_not be_valid
    expect(user.errors[:email]).to include("has already been taken")
  end

  # メールアドレスのフォーマットの検証
  describe "mail" do
    context "Correct format" do
      it "is OK" do
        @user.email = 'user@example.com'
        expect(@user).to be_valid
      end
    end
    context "Incorrect format" do
      it "is NG" do
        @user.email = 'user@example,com'
        expect(@user).to_not be_valid
        expect(@user.errors[:email]).to include("is invalid")
      end
    end
  end

  # nameの長さの検証
  describe "name length" do
    context "length 50 txt" do
      it "is OK" do
        @user.name = 'a' * 50
        expect(@user).to be_valid
      end
    end
    context "length 51 txt" do
      it "is NG" do
        @user.name = 'a' * 51
        expect(@user).to_not be_valid
        expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
      end
    end
  end
end

1ブロックづつ説明します

重複のチェックの際に使用するので、事前にテスト対象のオブジェクトとしてインスタンス化しておきます。

spec/models/user_spec.rb
before do
  @user = User.create(
    name:    "Yamada Taro",
    user_id: "taro",
    email:   "taro@example.com",
  )
end

name、user_id、emailがあれば有効な状態であること

spec/models/user_spec.rb
  # name、user_id、emailがあれば有効な状態であること
  it "is valid with a name, user_id, email" do
    user = User.new(
      name:    "Sato Taro",
      user_id: "sato",
      email:   "sato@example.com",
    )
    expect(user).to be_valid
  end

expect(user).to be_valid は、user が妥当(valid)なことを期待するという意味です。

RSpecではこうしたエクスペクテーション(expect)を書くため、toto_notを用いています。

nameがなければ無効な状態であること(nameは必須項目)

spec/models/user_spec.rb
it "is invalid without a name" do
  user = User.new(name: nil)
  user.valid?
  expect(user.errors[:name]).to include("can't be blank")
end

テストでは、user.nameにエラーメッセージ("can't be blank")が付いてることを期待(expect)しています。

メールアドレスがなければ無効な状態であること(emailは必須項目)

spec/models/user_spec.rb
it "is invalid without an email address" do
  user = User.new(email: nil)
  user.valid?
  expect(user.errors[:email]).to include("can't be blank")
end

valid? が false なら、errors.messagesにエラー内容が格納されます。今回は、それが、"can't be blank"であることを期待(expect)しています。

重複したメールアドレスなら無効な状態であること

spec/models/user_spec.rb
it "is invalid with a duplicate email address" do
  user = User.new(email: "taro@example.com")
  user.valid?
  expect(user.errors[:email]).to include("has already been taken")
end

valid? が false なら、errors.messagesにエラー内容が格納されます。今回は、それが、"has already been taken"であることを期待(expect)しています。

メールアドレスが小文字化されていること

spec/models/user_spec.rb
it "email addresses should be saved as lower-case" do
  mixed_case_email = "TANAKA@example.com"
  @user.email = mixed_case_email
  @user.save
  expect(@user[:email]).to eq("tanaka@example.com")
end

@user[:email]の値 = "tanaka@example.com"TRUE であることを期待(expect)しています。

メールアドレスの一意性の検証

spec/models/user_spec.rb
it "email is unique" do
  user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
  expect(user).to_not be_valid
  expect(user.errors[:email]).to include("has already been taken")
end

taro@example.comはすでに保存されたアドレスなので、バリデーションは通らないことと、エラーメッセージは"has already been taken"であることを期待(expect)しています。

メールアドレスのフォーマットの検証

  • ,comのカンマがあるのは、無効である
  • .comであれば、有効である(すくなくとも無効ではない)

ことを

Userモデルのバリデーション部分に記載しているので、テストがパスしないことを期待(expect)しています。

spec/models/user_spec.rb
describe "mail" do
  context "Correct format" do
    it "is OK" do
      @user.email = 'user@example.com'
      expect(@user).to be_valid
    end
  end
  context "Incorrect format" do
    it "is NG" do
      @user.email = 'user@example,com'
      expect(@user).to_not be_valid
      expect(@user.errors[:email]).to include("is invalid")
    end
  end
end

nameの長さの検証

nameの中身の文字数が

  • 50文字までは有効
  • 51文字以上は無効

であることをUserモデルのバリデーション部分に記載しているので、テストがパスしないことを期待(expect)しています。

spec/models/user_spec.rb
describe "name length" do
  context "length 50 txt" do
    it "is OK" do
      @user.name = 'a' * 50
      expect(@user).to be_valid
    end
  end
  context "length 51 txt" do
    it "is NG" do
      @user.name = 'a' * 51
      expect(@user).to_not be_valid
      expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
    end
  end
end

テスト実行結果

bundle exec rspec spec で実行できます。


$ bundle exec rspec spec
                                                                                                                                                                                                   
StaticPagesController
  GET #top
    returns http success
  GET #about
    returns http success

UsersController
  GET #new
    returns http success

User
  is valid with a name, user_id, email
  is invalid without a name
  is invalid without an email address
  is invalid with a duplicate email address
  email addresses should be saved as lower-case
  email is unique
  mail
    Correct format
      is OK
    Incorrect format
      is NG
  name length
    length 50 txt
      is OK
    length 51 txt
      is NG

Finished in 0.34401 seconds (files took 1.19 seconds to load)
13 examples, 0 failures

全て通りました。

まとめ

  • テストとは基本的に、期待する値と、実際に生成される値を比較検証であると実感
  • テストの妥当性を検証するために、期待する値を変更したりして、テストをパスしないことを確かめる必要性を実感
  • it ... end 中にあるブロックは、1つのテストをまとめる役割をしています。これによって、テストがパスしなかった場合、どこでコケたか、原因究明がわかりやすくなる
  • テストをパスするということは、デプロイのたびにビクビクせず、自信を持てると実感

まだまだ勉強中なので、問題点があればご指摘ください。

参考

6
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?