2
2

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 3 years have passed since last update.

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #10.5 RSpecでTutorialのテストを書き直す

Last updated at Posted at 2019-08-18

こんなことが分かる

  • TutorialでのMinitestをRSpecで変換する方法
  • Controller/Model spec, Request spec, System specなどの違い

前回:#10 リメンバーミー機能編
次回:#11 プロフィール編集編

注意点

  • __Tutorial9章まで__または__本記事#10まで__のテストを__RSpec__で見直します
  • Minitest → RSpecに移行する都合上、__異なるファイル名を使用__する場合があります
  • リファクタリングの都合上、__別テストに移行、再定義、example名の変更__などを行なっています

リンク先:
Ruby on Rails Tutorial
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #1 準備編

当記事の背景

何specで書けばいいのかを区別するためです。

テストに関するガイドライン

  • Contoller/View specは極力不使用
  • Request specは結合テストで使用
  • System specはブラウザ上のテストに使用

Request spec → コントローラやヘルパーなどがどう行動したのか
System spec → ブラウザ画面がどうなっているのか

この基準で進めます。

Tutorial9章/記事#10までのRSpec公開

以下の順で公開します。

準備系

  • rails_helper.rb
  • factories
  • support

テスト系

  • models
  • helpers
  • requests
  • systems

rails_helper.rb

意義:RSpecの設定

spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end

RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
  config.include FactoryBot::Syntax::Methods
  config.include ApplicationHelpers
  
  config.before(:each) do |example|
    if example.metadata[:type] == :system
      driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
    end
  end
end

factories

意義:テスト用のActive Record

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "Michael Example" }
    email { "michael@example.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end

support

意義:テスト用のヘルパー

spec/support/application_helper.rb
module ApplicationHelpers
  
  def is_logged_in?
    !session[:user_id].nil?
  end
end

models

意義:モデルのテスト

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  
  let(:user) { create(:user) }
  
  describe "User" do
    it "should be valid" do
      expect(user).to be_valid
    end
  end
  
  describe "name" do
    it "gives presence" do
      user.name = "  "
      expect(user).to be_invalid
    end
    
    context "50 characters" do
      it "is not too long" do
        user.name = "a" * 50
        expect(user).to be_valid
      end
    end
    
    context "51 characters" do
      it "is too long" do
        user.name = "a" * 51
        expect(user).to be_invalid
      end
    end
  end
  
  describe "email" do
    it "gives presence" do
      user.email = "  "
      expect(user).to be_invalid
    end
    
    context "254 characters" do
      it "is not too long" do
        user.email = "a" * 243 + "@example.com"
        expect(user).to be_valid
      end
    end
    
    context "255 characters" do
      it "is too long" do
        user.email = "a" * 244 + "@example.com"
        expect(user).to be_invalid
      end
    end
    
    it "should accept valid addresses" do
      user.email = "user@example.com"
      expect(user).to be_valid
      user.email = "USER@foo.COM"
      expect(user).to be_valid
      user.email = "A_US-ER@foo.bar.org"
      expect(user).to be_valid
      user.email = "first.last@foo.jp"
      expect(user).to be_valid
      user.email = "alice+bob@baz.cn"
      expect(user).to be_valid
    end
    
    it "should reject invalid addresses" do
      user.email = "user@example,com"
      expect(user).to be_invalid
      user.email = "user_at_foo.org"
      expect(user).to be_invalid
      user.email = "user.name@example."
      expect(user).to be_invalid
      user.email = "foo@bar_baz.com"
      expect(user).to be_invalid
      user.email = "foo@bar+baz.com"
      expect(user).to be_invalid
      user.email = "foo@bar..com"
      expect(user).to be_invalid
    end
    
    it "should be unique" do
      duplicate_user = user.dup
      duplicate_user.email = user.email.upcase
      user.save!
      expect(duplicate_user).to be_invalid
    end
    
    it "should be saved as lower-case" do
      user.email = "Foo@ExAMPle.CoM"
      user.save!
      expect(user.reload.email).to eq 'foo@example.com'
    end
  end
  
  describe "password and password_confirmation" do
    it "should be present (nonblank)" do
      user.password = user.password_confirmation = " " * 6
      expect(user).to be_invalid
    end
    
    context "5 characters" do
      it "is too short" do
        user.password = user.password_confirmation = "a" * 5
        expect(user).to be_invalid
      end
    end
      
    context "6 characters" do
      it "is not too short" do
        user.password = user.password_confirmation = "a" * 6
        expect(user).to be_valid
      end
    end
  end
  
  describe "User model methods" do
    describe "authenticated?" do
      it "return false for a user with nil digest" do
        expect(user.authenticated?('')).to be_falsey
      end
    end
  end
end

helpers

意義:ヘルパーのテスト

spec/helpers/application_helper_spec.rb
require 'rails_helper'

RSpec.describe ApplicationHelper, type: :helper do
  
  describe "#full_title" do
    context "page_title is empty" do
      it "removes symbol" do
        expect(helper.full_title).to eq('Lantern Lantern')
      end
    end
    
    context "page_title is not empty" do
      it "returns title and application name where contains symbol" do
        expect(helper.full_title('hoge')).to eq('hoge | Lantern Lantern')
      end
    end
  end
end
spec/helpers/sessions_helper_spec.rb
require 'rails_helper'

RSpec.describe SessionsHelper, type: :helper do
  
  let(:user) { create(:user) }
  
  describe "#current_user" do
    it "returns right user when session is nil" do
      remember(user)
      expect(current_user).to eq user
      expect(is_logged_in?).to be_truthy
    end
    
    it "returns nil when remember digest is wrong" do
      remember(user)
      user.update_attribute(:remember_digest, User.digest(User.new_token))
      expect(current_user).to be_nil
    end
  end
end

requests

意義:結合テスト

spec/requests/users_logins_spec.rb
require 'rails_helper'

RSpec.describe "UsersLogins", type: :request do
  include SessionsHelper
  
  let(:user) { create(:user) }
  
  def post_invalid_information
    post login_path, params: {
      session: {
        email: "",
        password: ""
      }
    }
  end
  
  def post_valid_information(remember_me = 0)
    post login_path, params: {
      session: {
        email: user.email,
        password: user.password,
        remember_me: remember_me
      }
    }
  end
  
  describe "GET /login" do
    context "invalid form information" do
      it "fails having a danger flash message" do
        get login_path
        post_invalid_information
        expect(flash[:danger]).to be_truthy
        expect(is_logged_in?).to be_falsey
      end
    end
    
    context "valid form information" do
      it "succeeds having no danger flash message" do
        get login_path
        post_valid_information
        expect(flash[:danger]).to be_falsey
        expect(is_logged_in?).to be_truthy
        follow_redirect!
        expect(request.fullpath).to eq '/users/1'
      end
      
      it "succeeds logout" do
        get login_path
        post_valid_information
        expect(is_logged_in?).to be_truthy
        follow_redirect!
        expect(request.fullpath).to eq '/users/1'
        delete logout_path
        expect(is_logged_in?).to be_falsey
        follow_redirect!
        expect(request.fullpath).to eq '/'
      end
      
      it "does not log out twice" do
        get login_path
        post_valid_information
        expect(is_logged_in?).to be_truthy
        follow_redirect!
        expect(request.fullpath).to eq '/users/1'
        delete logout_path
        expect(is_logged_in?).to be_falsey
        follow_redirect!
        expect(request.fullpath).to eq '/'
        delete logout_path
        follow_redirect!
        expect(request.fullpath).to eq '/'
      end
      
      it "succeeds remember_token because of check remember_me" do
        get login_path
        post_valid_information(1)
        expect(is_logged_in?).to be_truthy
        expect(cookies[:remember_token]).not_to be_empty
      end
      
      it "has no remember_token because of check remember_me" do
        get login_path
        post_valid_information(0)
        expect(is_logged_in?).to be_truthy
        expect(cookies[:remember_token]).to be_nil
      end
      
      it "has no remember_token when users logged out and logged in" do
        get login_path
        post_valid_information(1)
        expect(is_logged_in?).to be_truthy
        expect(cookies[:remember_token]).not_to be_empty
        delete logout_path
        expect(is_logged_in?).to be_falsey
        expect(cookies[:remember_token]).to be_empty
      end
    end
  end
end
spec/requests/users_signups_spec.rb
require 'rails_helper'

RSpec.describe "UsersSignups", type: :request do
  include SessionsHelper
  
  def post_invalid_information
    post signup_path, params: {
      user: {
        name: "",
        email: "user@invalid",
        password: "foo",
        password_confirmation: "bar"
      }
    }
  end
  
  def post_valid_information
    post signup_path, params: {
      user: {
        name: "Example User",
        email: "user@example.com",
        password: "password",
        password_confirmation: "password"
      }
    }
  end
  
  describe "GET /signup" do
    it "is invalid signup information" do
      get signup_path
      expect { post_invalid_information }.not_to change(User, :count)
      expect(is_logged_in?).to be_falsey
    end
    
    it "is valid signup information" do
      get signup_path
      expect { post_valid_information }.to change(User, :count).by(1)
      expect(is_logged_in?).to be_truthy
      follow_redirect!
      expect(request.fullpath).to eq '/users/1'
    end
  end
end

systems

意義:ブラウザテスト

spec/systems/login_spec.rb
require 'rails_helper'

RSpec.describe "Logins", type: :system do
  
  let(:user) { create(:user) }
  
  def submit_with_invalid_information
    fill_in 'メールアドレス', with: ''
    fill_in 'パスワード', with: ''
    find(".form-submit").click
  end
  
  def submit_with_valid_information(remember_me = 0)
    fill_in 'メールアドレス', with: user.email
    fill_in 'パスワード', with: 'password'
    check 'session_remember_me' if remember_me == 1
    find(".form-submit").click
  end

  describe "Login" do
    context "invalid" do
      it "has no information and has flash danger message" do
        visit login_path
        expect(page).to have_selector '.login-container'
        submit_with_invalid_information
        expect(current_path).to eq login_path
        expect(page).to have_selector '.login-container'
        expect(page).to have_selector '.alert-danger'
      end
      
      it "deletes flash messages when users input invalid information then other links" do
        visit login_path
        submit_with_invalid_information
        expect(current_path).to eq login_path
        visit root_path
        expect(page).not_to have_selector '.alert-danger'
      end
    end
      
    context "valid" do
      it "has valid information and will link to user path" do
        visit login_path
        submit_with_valid_information
        expect(current_path).to eq user_path(1)
        expect(page).to have_selector '.show-container'
      end
      
      it "contains logout button without login button at user path" do
        visit login_path
        submit_with_valid_information
        expect(current_path).to eq user_path(1)
        expect(page).to have_selector '.btn-logout-extend'
        expect(page).not_to have_selector '.btn-login-extend'
      end
    end
  end
    
  describe "Logout" do 
    it "contains login button without logout button at root path" do
      visit login_path
      submit_with_valid_information
      expect(current_path).to eq user_path(1)
      expect(page).to have_selector '.btn-logout-extend'
      expect(page).not_to have_selector '.btn-login-extend'
      click_on 'ログアウト'
      expect(current_path).to eq root_path
      expect(page).to have_selector '.home-container'
      expect(page).to have_selector '.btn-login-extend'
      expect(page).not_to have_selector '.btn-logout-extend'
    end
  end
end
spec/systems/signup_spec.rb
require 'rails_helper'

RSpec.describe "Signups", type: :system do
  
  def submit_with_invalid_information
    fill_in '名前', with: ''
    fill_in 'メールアドレス', with: 'user@invalid'
    fill_in 'パスワード', with: 'foo'
    fill_in 'パスワード(再入力)', with: 'bar'
    find(".form-submit").click
  end
  
  def submit_with_valid_information
    fill_in '名前', with: 'Example User'
    fill_in 'メールアドレス', with: 'user@example.com'
    fill_in 'パスワード', with: 'password'
    fill_in 'パスワード(再入力)', with: 'password'
    find(".form-submit").click
  end
    
  it "is invalid because it has no name" do
    visit signup_path
    submit_with_invalid_information
    expect(current_path).to eq signup_path
    expect(page).to have_selector '#error_explanation'
  end
  
  it "is valid because it fulfils form information" do
    visit signup_path
    expect { submit_with_valid_information }.to change(User, :count).by(1)
    expect(current_path).to eq user_path(1)
    expect(page).not_to have_selector '#error_explanation'
  end
end
spec/systems/site_layout_spec.rb
require 'rails_helper'

RSpec.describe "SiteLayouts", type: :system do
  
  describe "home layout" do
    it "contains root link" do
      visit root_path
      expect(page).to have_link nil, href: root_path
    end
  
    it "contains signup link" do
      visit root_path
      expect(page).to have_link 'はじめる', href: signup_path
    end
    
    it "contains login link" do
      visit root_path
      expect(page).to have_link 'ログイン', href: login_path
    end
    
    it "returns title with 'Lantern Lantern'" do
      visit root_path
      expect(page).to have_title 'Lantern Lantern'
    end
  end
  
  describe "about layout" do
    it "returns title with 'About | Lantern Lantern'" do
      visit about_path
      expect(page).to have_title 'About | Lantern Lantern'
    end
  end
end

前回:#10 リメンバーミー機能編
次回:#11 プロフィール編集編

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?