###### tags: `YouTube` # RSpec 入門 ## 環境構築 本講座では、Docker を使って開発を行います。 しかし、ゼロから `Dockerfile` や `docker-compose.yml` を作成するといったことは行いません。**Rails 7.0.4** と **Ruby 3.1.3** に対応した Docker 化されたアプリを使います。 今回、Nick Janetakis 氏が公開している下記リポジトリを採用させて頂きました。 https://github.com/nickjj/docker-rails-example 環境構築の詳細・立ち上げるコンテナの役割については、下記の書籍で解説しています。 https://zenn.dev/farstep/books/7f169cdc597ada ### リポジトリのクローン ```bash $ git clone git@github.com:nickjj/docker-rails-example.git rspec_tutorial $ cd rspec_tutorial ``` ```bash $ cp .env.example .env ``` ### 環境変数の編集(`.env`) ```diff env - export COMPOSE_PROJECT_NAME=hellorails + export COMPOSE_PROJECT_NAME=rspec_tutorial ``` ```diff env - #export POSTGRES_DB=hello + export POSTGRES_DB=rspec_tutorial ``` ### アプリケーションのタイトルの編集(`app/views/layouts/application.html.erb`) ```diff html - <title>Docker + Rails</title> + <title>Rspec Tutorial</title> ``` ### インストールする gem の追加(`Gemfile`) ```diff ruby source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.3" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.4" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] gem "sprockets-rails" # Use postgresql as the database for Active Record gem "pg", "~> 1.1" # Use the Puma web server [https://github.com/puma/puma] gem "puma", "~> 6.0" # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails] gem "jsbundling-rails" # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails" # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] gem "stimulus-rails" # Bundle and process CSS [https://github.com/rails/cssbundling-rails] gem "cssbundling-rails" # Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" # Use Redis adapter to run Action Cable in production gem "redis", "~> 5.0" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" # Execute jobs in the background [https://github.com/mperham/sidekiq] gem "sidekiq", "~> 7.0" group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri mingw x64_mingw ] # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", require: false end group :development do # Use console on exceptions pages [https://github.com/rails/web-console] gem "web-console" # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] gem "rack-mini-profiler" # Speed up commands on slow machines / big apps [https://github.com/rails/spring] # gem "spring" end group :test do # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem "capybara" gem "selenium-webdriver" gem "webdrivers" + gem "rspec-rails" + gem "factory_bot_rails" + gem "faker" end ``` | gem | 用途 | | -------- | -------- | | rspec-rails | テストフレームワーク | | factory_bot_rails | テスト用オブジェクトの生成 | | faker | ダミーデータの作成 | ### データベースの設定(`docker-compose.yml`・`config/database.yml`) ```diff yml services: postgres: deploy: resources: limits: cpus: "${DOCKER_POSTGRES_CPUS:-0}" memory: "${DOCKER_POSTGRES_MEMORY:-0}" environment: POSTGRES_USER: "${POSTGRES_USER}" POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_DB: "${POSTGRES_DB}" image: "postgres:15.1-bullseye" profiles: ["postgres"] restart: "${DOCKER_RESTART_POLICY:-unless-stopped}" stop_grace_period: "3s" volumes: - "postgres:/var/lib/postgresql/data" ``` ```diff yml default: &default adapter: "postgresql" encoding: "unicode" + database: "<%= ENV.fetch("POSTGRES_DB") { "rspec_tutorial" } %>" username: "<%= ENV.fetch("POSTGRES_USER") { "hello" } %>" password: "<%= ENV.fetch("POSTGRES_PASSWORD") { "password" } %>" host: "<%= ENV.fetch("POSTGRES_HOST") { "postgres" } %>" port: "<%= ENV.fetch("POSTGRES_PORT") { 5432 } %>" # http://guides.rubyonrails.org/configuring.html#database-pooling pool: "<%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>" development: <<: *default + database: <%= ENV.fetch("POSTGRES_DB") { "rspec_tutorial" } %>_development test: <<: *default + database: <%= ENV.fetch("POSTGRES_DB") { "rspec_tutorial" } %>_test production: <<: *default + database: <%= ENV.fetch("POSTGRES_DB") { "rspec_tutorial" } %>_production ``` ### 自動生成ファイルの設定(`config/application.rb`) ```diff ruby require_relative "boot" require "rails/all" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Hello class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 # Log to STDOUT because Docker expects all processes to log here. You could # then collect logs using journald, syslog or forward them somewhere else. logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) # Set Redis as the back-end for the cache. config.cache_store = :redis_cache_store, { url: ENV.fetch("REDIS_URL") { "redis://redis:6379/1" }, namespace: "cache" } # Set Sidekiq as the back-end for Active Job. config.active_job.queue_adapter = :sidekiq # Mount Action Cable outside the main process or domain. config.action_cable.mount_path = nil config.action_cable.url = ENV.fetch("ACTION_CABLE_FRONTEND_URL") { "ws://localhost:28080" } # Only allow connections to Action Cable from these domains. origins = ENV.fetch("ACTION_CABLE_ALLOWED_REQUEST_ORIGINS") { "http:\/\/localhost*" }.split(",") origins.map! { |url| /#{url}/ } config.action_cable.allowed_request_origins = origins + # Customizing Rails Generators + config.generators do |g| + g.assets false # CSSやJavaScriptファイルを生成しない + g.helper false # ヘルパーを生成しない + g.jbuilder false # .json.jbuilderファイルを生成しない + g.test_framework :rspec, # テストフレームワークとしてRSpecを指定 + fixtures: false, # テストデータを作るfixtureを作成しない + request_specs: false, # リクエストスペックを作成しない + view_specs: false, # ビュー用のスペックを作成しない + helper_specs: false, # ヘルパー用のスペックを作成しない + routing_specs: false # ルーティングのスペックを作成しない + end end end ``` | 記述 | 理由 | | -------- | -------- | | `fixtures: false` | FactoryBot を使用するため | | `request_specs: false` | 結合テスト(System Spec)でカバーできるため | | `view_specs: false` | 結合テスト(System Spec)でカバーできるため | | `helper_specs: false` | 今回ヘルパーを作成しないため | | `routing_specs: false` | 結合テスト(System Spec)でカバーできるため | ### Docker イメージの構築(ビルド) ```bash $ docker-compose build ``` ### データベースの作成・マイグレーションの実行 ```bash $ docker-compose run --rm web bash ``` ```bash $ rails db:create $ rails db:migrate ``` ## アプリケーションの作成 ### モデル・テーブル・コントローラ・ビューの作成 ```bash $ rails g scaffold tweet content ``` ```ruby class CreateTweets < ActiveRecord::Migration[7.0] def change create_table :tweets do |t| t.string :content, null:false t.timestamps end end end ``` ### エラーメッセージ config/locales/en.yml ```yaml en: errors: messages: not_saved: one: "1 error prohibited this %{resource} from being saved:" other: "%{count} errors prohibited this %{resource} from being saved:" ``` ### Tailwind CSS による UI の作成 app/views/layouts/application.html.erb ```erb <% if flash[:notice] %> <div class="p-4 mb-4 text-md text-blue-700 text-center font-bold"> <%= notice %> </div> <% end %> <% if flash[:alert] %> <div class="p-4 mb-4 text-md text-red-700 text-center font-bold"> <%= alert %> </div> <% end %> <main class="container mx-auto sm:w-10/12 lg:w-9/12 mb-8 z-0"> <%= yield %> </main> ``` app/views/tweets/_form.html.erb ```erb <div class='flex flex-wrap justify-center'> <%= form_with model: tweet, class: "xl:w-8/12 md:w-10/12" do |f| %> <% if tweet.errors.any? %> <div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4 mb-5" role="alert"> <p class="font-bold"> <%= I18n.t("errors.messages.not_saved", count: tweet.errors.count, resource: tweet.class.model_name.human.downcase) %> </p> <ul> <% tweet.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="mb-6"> <%= f.label :content, class: "mb-2 block text-sm text-gray-600" %> <%= f.text_area :content, rows: "3", class: "w-full rounded-md border border-gray-300 py-2 px-3 placeholder:text-gray-300 focus:border-indigo-300 focus:outline-none focus:ring focus:ring-indigo-100" %> </div> <%= f.submit class: "inline-flex w-full items-center justify-center rounded-md bg-indigo-500 p-3 text-white duration-100 ease-in-out hover:bg-indigo-600 focus:outline-none cursor-pointer" %> <% end %> </div> ``` app/views/tweets/_tweet.html.erb ```erb <div id="<%= dom_id tweet %>" class="text-center mb-5"> <p class="text-xl"> <strong>Content:</strong> <%= tweet.content %> </p> </div> ``` app/views/tweets/edit.html.erb ```erb <div class="mb-8 text-center"> <span class="text-3xl font-bold"> Editing tweet </span> </div> <%= render "form", tweet: @tweet %> <br> <div class="text-center"> <%= link_to "Show this tweet", @tweet %> | <%= link_to "Back to tweets", tweets_path %> </div> ``` app/views/tweets/index.html.erb ```erb <div class="mb-8 text-center"> <span class="text-3xl font-bold"> Tweets </span> </div> <div class="flex flex-wrap"> <div class="flex-none w-full px-6"> <%= link_to new_tweet_path, class: "mb-4 inline-flex items-center justify-center rounded-md bg-indigo-500 p-3 text-white duration-100 ease-in-out hover:bg-indigo-600 focus:outline-none" do %> New tweet <% end %> <div class="relative flex flex-col min-w-0 mb-6 break-words bg-white border-0 border-transparent border-solid shadow-soft-xl rounded-2xl bg-clip-border"> <div class="flex-auto pb-2"> <div class="p-0 overflow-x-auto"> <table class="items-center w-full align-top border-gray-200 text-slate-500"> <thead class="align-bottom"> <tr> <th class="p-3 font-bold text-left uppercase align-middle bg-transparent border-b border-gray-200 shadow-none text-lg border-b-solid tracking-none whitespace-nowrap text-slate-400 opacity-70">Content</th> <th class="p-3 bg-transparent border-b border-gray-200 border-solid"></th> </tr> </thead> <tbody class="align-bottom"> <% @tweets.each do |tweet| %> <tr> <td class="p-3 align-middle bg-transparent border-b whitespace-nowrap shadow-transparent"> <p class="font-semibold leading-tight text-md"><%= tweet.content %></p> </td> <td class="p-3 align-middle bg-transparent border-b whitespace-nowrap shadow-transparent"> <%= link_to tweet, class: "font-semibold leading-tight text-md text-slate-400" do %> Show this tweet <% end %> </td> </tr> <% end %> </tbody> </table> </div> </div> </div> </div> </div> ``` app/views/tweets/new.html.erb ```erb <div class="mb-8 text-center"> <span class="text-3xl font-bold"> New tweet </span> </div> <%= render "form", tweet: @tweet %> <br> <div class="text-center"> <%= link_to "Back to tweets", tweets_path %> </div> ``` app/views/tweets/show.html.erb ```erb <div class="mb-8 text-center"> <span class="text-3xl font-bold"> Tweet </span> </div> <%= render @tweet %> <div class="text-center"> <%= link_to "Edit this tweet", edit_tweet_path(@tweet) %> | <%= link_to "Back to tweets", tweets_path %> <%= button_to "Destroy this tweet", @tweet, method: :delete, class: "font-bold text-red-500" %> </div> ``` ## RSpec によるテスト ### RSpec のセットアップ ```bash $ rails g rspec:install ``` | 生成されるファイル | 役割 | | -------- | -------- | | `.rspec` | 基本設定ファイル | | `spec/rails_helper.rb` | Rails 特有の設定を記述するファイル | | `spec_helper.rb` | RSpec の全体的な設定を記述するファイル | .rspec ```ruby: --require spec_helper --format documentation ``` 表示の出力をキレイにする `--format documentation` というオプションを追加。 spec/rails_helper.rb ```ruby RSpec.configure do |config| config.include FactoryBot::Syntax::Methods end ``` テスト用オブジェクトを生成するときに `create()` や `build()` が使えるようになる。(**シンタックスシュガー**) ```ruby # 【example】 # before tweet = FactoryBot.create(:tweet) # after tweet = create(:tweet) ``` 環境変数 `RAILS_ENV` を `test` に変更する。 spec/rails_helper.rb ```diff ruby - ENV['RAILS_ENV'] ||= 'test' + ENV['RAILS_ENV'] = 'test' ``` ### Model Spec を書く **Model Spec** とはバリデーション等の Rails のモデルのテストをする。 先ほど、Scaffold によって `spec/models/tweet_spec.rb` が生成されているはず。 このファイルに Model Spec を記述していく。 最初に、FactoryBot と Faker を使ってテストデータを生成する準備を行う。 ```bash $ mkdir spec/factories && touch spec/factories/tweets.rb ``` spec/factories/tweets.rb ```ruby FactoryBot.define do factory :tweet do # 英数字のランダムな文字列を生成する(100文字) content { Faker::Lorem.characters(number: 100) } end end ``` 上記の記述により、`build(:tweet)` といったコードを書くことで、テストデータを生成できる。 今回、tweets テーブルの content カラムは - 空でない - 140 文字以内 という仕様があると想定する。すると、テストコードは下記のようになる。 spec/models/tweet_spec.rb ```ruby require 'rails_helper' RSpec.describe Tweet, type: :model do let(:tweet) { build(:tweet) } it "is valid with a valid content" do expect(tweet).to be_valid end it "is not valid without a content" do tweet.content = '' expect(tweet).to_not be_valid end it "is not valid with a content longer than 140" do tweet.content = Faker::Lorem.characters(number: 141) expect(tweet).to_not be_valid end end ``` #### describe・context・it について ![](https://i.imgur.com/8X8EgZ7.png) **describe** には、テストの対象が何かを記述する。 **context** には、特定の条件が何かを記述する。 (例. ログインしている場合としていない場合を分ける) **it** には、アウトプットが何かを記述する。 (例. 期待する内容) #### `build()` について `build()` はオブジェクト化行う。データベースに登録されない。 `build(:tweet)` とすると、content カラムに 100 文字の英数字が格納された Tweet オブジェクトが生成される。 #### let について - describe か context の中でのみ使用可能。 - 定義した定数が初めて出てきたときに評価される(**遅延評価**)。 `let(:tweet) { build(:tweet) }` `expect(tweet).to be_valid` と記述すると 1. `expect(tweet)` が実行され `tweet` が初めて出てくる。 2. `tweet` が build される。 3. `to` が実行される。 4. `be_valid` が実行される。 という流れになる。 #### be_valid について `be_valid` とは、Rails の ActiveRecord が提供するメソッドで、モデルオブジェクトのバリデーションが成功したかどうか真偽値で返す。 ``` $ bundle exec rspec ``` ```ruby Tweet is valid with a valid content is not valid without a content (FAILED - 1) is not valid with a content longer than 140 (FAILED - 2) Failures: 1) Tweet is not valid without a content Failure/Error: expect(tweet).to_not be_valid expected #<Tweet id: nil, content: "", created_at: nil, updated_at: nil> not to be valid # ./spec/models/tweet_spec.rb:10:in `block (2 levels) in <top (required)>' 2) Tweet is not valid with a content longer than 140 Failure/Error: expect(tweet).to_not be_valid expected #<Tweet id: nil, content: "w44f4jow0b97azkzcd35qbj3680deqscw3t3k5ver7mnm6xabn...", created_at: nil, updated_at: nil> not to be valid # ./spec/models/tweet_spec.rb:14:in `block (2 levels) in <top (required)>' Finished in 0.12862 seconds (files took 6.77 seconds to load) 3 examples, 2 failures ``` バリデーションを記述していないため、テストが失敗する。 app/models/tweet.rb ```ruby class Tweet < ApplicationRecord validates :content, presence: true validates :content, length: { maximum: 140 } end ``` バリデーションを記述した後、テストが通る。 ```ruby Tweet is valid with a valid content is not valid without a content is not valid with a content longer than 140 Finished in 0.07432 seconds (files took 3.69 seconds to load) 3 examples, 0 failures ``` 最初にテストを書き、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、というスタイル(**テスト開発駆動**) ```ruby class Tweet < ApplicationRecord validates :content, presence: true, length: { maximum: 140 } end ``` ### System Spec を書く **System Spec** とは **Capybara** を使ってブラウザの操作をシミュレートするテストのこと。 #### Capybara について Capybara は結合テストや E2E テスト(システム全体を通したテスト)等に使用されるツールである。Capybara ではアプリケーションを実際に動かしているかのようなシュミレートによってテストを行ってくれる。 Capybara を動作させるときには「ドライバ」と言われる実行環境を選択できる。 今回は、高速に動作する **rack_test** を採用する。(JavaScript のテスト項目がある場合は Selenium を使う必要がある。) `spec/spec_helper.rb` で、System Spec を実行する際に rack_test をドライバとして選択することを明記する。 ```ruby RSpec.configure do |config| config.before(:each, type: :system) do driven_by :rack_test end ...略 end ``` #### PagesController の system spec ```bash $ rails g rspec:system pages ``` spec/system/pages_spec.rb ```ruby require 'rails_helper' RSpec.describe "Pages", type: :system do describe '#home' do it 'renders the top page' do visit root_path expect(page.status_code).to eq(200) end end end ``` #### TweetsController の system spec ```bash $ rails g rspec:system tweets ``` spec/system/tweets_spec.rb ```ruby require 'rails_helper' RSpec.describe Tweet, type: :system do let!(:tweet) { create(:tweet) } describe 'List screen' do before do visit tweets_path end describe '#index' do it 'displays content of the tweet' do expect(page).to have_content tweet.content end it 'displays a link to the details screen' do expect(page).to have_link 'Show this tweet', href: "/tweets/#{tweet.id}" end it 'displays a link to post a tweet' do expect(page).to have_link 'New tweet', href: "/tweets/new" end end end describe 'Post screen' do before do visit new_tweet_path end describe '#new' do it "displays a form for content" do expect(page).to have_field 'tweet[content]' end it 'displays a Create Tweet Button' do expect(page).to have_button 'Create Tweet' end it 'displays a link to back to list screen' do expect(page).to have_link 'Back to tweets', href: "/tweets" end end describe '#create' do it "redirects to the details screen with a valid content" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 100) click_button 'Create Tweet' expect(current_path).to eq tweet_path(Tweet.last) end it "displays created tweet with a valid content" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 100) click_button 'Create Tweet' expect(page).to have_content Tweet.last.content end it "displays message 'Content can't be blank' without content" do click_button 'Create Tweet' expect(page).to have_content "Content can't be blank" end it "returns a 422 Unprocessable Entity status without content" do click_button 'Create Tweet' expect(page).to have_http_status(422) end it "displays message 'Content is too long (maximum is 140 characters)' with a content longer than 140" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 141) click_button 'Create Tweet' expect(page).to have_content "Content is too long (maximum is 140 characters)" end it "returns a 422 Unprocessable Entity status with a content longer than 140" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 141) click_button 'Create Tweet' expect(page).to have_http_status(422) end end end describe 'Details screen' do before do visit tweet_path(tweet) end describe '#show' do it "displays content of the tweet" do expect(page).to have_content tweet.content end it "displays a link to the editing screen" do expect(page).to have_link 'Edit this tweet', href: "/tweets/#{tweet.id}/edit" end it "displays a link to back to list screen" do expect(page).to have_link 'Back to tweets', href: "/tweets" end end describe '#destroy' do it "decreases the number of tweet" do count = Tweet.count click_on 'Destroy this tweet' expect(Tweet.count).to eq (count - 1) end it "redirects to the list screen" do click_on 'Destroy this tweet' expect(current_path).to eq tweets_path end end end describe 'Editing screen' do before do visit edit_tweet_path(tweet) end describe '#edit' do it "displays the content in the form" do expect(page).to have_field 'tweet[content]', with: tweet.content end it 'displays a Update Tweet Button' do expect(page).to have_button 'Update Tweet' end it 'displays a link to the details screen' do expect(page).to have_link 'Show this tweet', href: "/tweets/#{tweet.id}" end it 'displays a link to back to list screen' do expect(page).to have_link 'Back to tweets', href: "/tweets" end end describe '#update' do it "redirects to the details screen with a valid content" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 100) click_button 'Update Tweet' expect(current_path).to eq tweet_path(Tweet.last) end it "displays created tweet with a valid content" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 100) click_button 'Update Tweet' expect(page).to have_content Tweet.last.content end it "displays message 'Content can't be blank' without content" do fill_in 'tweet[content]', with: "" click_button 'Update Tweet' expect(page).to have_content "Content can't be blank" end it "returns a 422 Unprocessable Entity status without content" do fill_in 'tweet[content]', with: "" click_button 'Update Tweet' expect(page).to have_http_status(422) end it "displays message 'Content is too long (maximum is 140 characters)' with a content longer than 140" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 141) click_button 'Update Tweet' expect(page).to have_content "Content is too long (maximum is 140 characters)" end it "returns a 422 Unprocessable Entity status with a content longer than 140" do fill_in 'tweet[content]', with: Faker::Lorem.characters(number: 141) click_button 'Update Tweet' expect(page).to have_http_status(422) end end end end ``` #### `create()` と `build()` について `build()` はオブジェクト化するだけ。データベースに登録されない。 下記はパスしないテストの例。 ```ruby let(:tweet) { build(:tweet) } it 'get the tweet' do expect(Tweet.first).to eq tweet end ``` `create()` はデータベースへアクセスし、インスタンスを保存する。 #### `let` と `let!` について `let` で指定されたものは **遅延評価** といって it の中で `tweet` が出てきたときにはじめて実行される。 `let!` で指定されたものは **事前評価** といって、各テストのブロック実行前に定義した定数を作る。 下記のようなテストにおいて、`let!` と `let` にするとテストがパスしない。 ```ruby it 'displays content of the tweet' do expect(page).to have_content tweet.content end ``` `let` を使った場合は、 1. `expect(page)` が実行される。 2. `.to` が実行される。 3. `have_content tweet.content` が実行される。(ここで、`tweet` が初めて登場する。まだ tweet が生成されていないため `have_content tweet.content` が失敗する。) (4. `tweet` が生成される。) という流れになる。 #### `before` について `before ~ do` で囲んだ処理は `it` の直前に毎回呼び出される。 ```ruby before do visit tweets_path end ``` と記述すると、`it` の前に毎回ツイートの一覧画面に遷移する。 #### 画面やフォームの検証について | メソッド | 意味 | | -------- | -------- | | `have_content` | ページ内に特定の文字列が表示されていることを検証する | | `have_link` | ページ内に特定のリンクが表示されていることを検証する | | `have_button` | ページ内に特定のボタンが表示されていることを検証する | | `have_field` | ページ内に特定の入力フィールドがあることを検証する( name 属性で判断する) | #### Capybara の基本メソッドについて | メソッド | 操作 | | -------- | -------- | | `visit` | 指定したページに遷移 | | `click_link` | 指定したリンク文字列を持つ a タグをクリック | | `fill_in` | 入力したい文字列を指定のフォームに入力 | | `click_button` | 指定したラベルを持つボタンをクリック | #### 422 Unprocessable Entity status について 処理できないエンティティ。REST において、入力値の検証の失敗(バリデーションエラー)を伝える目的で使用する場合もある。 #### SQL のログ表示について spec/rails_helper.rb ```diff ruby + # SQL Logs + ActiveRecord::Base.logger = Logger.new(STDOUT) RSpec.configure do |config| # 省略 end ``` ## 完成版のコード https://github.com/FarStep131/rspec_tutorial