11
1

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 1 year has passed since last update.

Railsのシステムテスト(Minitest)で「期待どおりに404エラーが発生したこと」を検証する方法

Last updated at Posted at 2022-03-15

Railsのシステムテスト(Minitest)で「期待どおりに404エラーが発生したこと」を検証する方法が意外と厄介だったので説明します。

対象バージョン

  • Rails 6.1.5
  • Capybara 3.36.0
  • selenium-webdriver 4.1.0

おことわり

この方法がベストなのか自信がないので、もっといい方法を知っている人がいたらコメント欄等教えてください🙏

404エラーの発生を検証したいユースケースの例

  • ログイン済みユーザーしか表示させたくないページに、未ログインのユーザーがアクセスしてきた場合に404エラーを返すことをシステムテストで検証したい

302リダイレクトでトップページに遷移し、「ログインしてください」のメッセージを表示する、という挙動もありそうですが、ここではあえて404エラーを表示するものとします。

ベースとなるテストコード

ここでは以下のようなテストコードを書いたものとします(上で書いたユースケースとは関係のない、単なる動作確認用のテストコードです)。

require "application_system_test_case"

class BlogsTest < ApplicationSystemTestCase
  setup do
    @blog = blogs(:one)
  end

  test "visiting a Blog" do
    # 存在しないidを指定して、意図的に404エラーを発生させる
    visit blog_url(@blog.id + 1)
  end
end

このテストを実行すると、次のようなエラーメッセージが表示され、テストが失敗します。

2022-03-15 08:59:43 +0900 Rack app ("GET /blogs/980190963" - (127.0.0.1)): #<ActiveRecord::RecordNotFound: Couldn't find Blog with 'id'=980190963>
E

Error:
BlogsTest#test_visiting_a_Blog:
ActiveRecord::RecordNotFound: Couldn't find Blog with 'id'=980190963
    app/controllers/blogs_controller.rb:63:in `set_blog'

rails test test/system/blogs_test.rb:16

Finished in 2.137874s, 0.4678 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

しかし、ここでは404エラーが発生するのはテストの失敗ではなく成功なので、「404エラーが期待どおり発生すればパス、発生しなければエラー」としたいです。

404エラーを検証するテストコードの書き方

今回はこんな方法でテストすることにしました。

class BlogsTest < ApplicationSystemTestCase
  setup do
    @blog = blogs(:one)
    @raise_server_errors = Capybara.raise_server_errors
  end

  teardown do
    Capybara.raise_server_errors = @raise_server_errors
  end

  test "visiting a Blog" do
    Capybara.raise_server_errors = false

    visit blog_url(@blog.id + 1)
    assert_text 'ActiveRecord::RecordNotFound'
  end
end

こうすると("ActiveRecord::RecordNotFound"のメッセージは表示されるものの)テストはパスします。

2022-03-15 09:04:50 +0900 Rack app ("GET /blogs/980190963" - (127.0.0.1)): #<ActiveRecord::RecordNotFound: Couldn't find Blog with 'id'=980190963>
.

Finished in 2.026935s, 0.4934 runs/s, 0.9867 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips

期待に反して404エラーが発生しなかった場合はテストが失敗します。

F

Failure:
BlogsTest#test_visiting_a_Blog [/Users/jnito/dev/sandbox/not-found-test-sandbox/test/system/blogs_test.rb:18]:
expected to find text "RecordNotFound" in "Title: MyString\nEdit | Back"

rails test test/system/blogs_test.rb:16

Finished in 4.355798s, 0.2296 runs/s, 0.2296 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips

変更点の解説

Capybara.raise_server_errors = falseを設定すると、404エラーが起きてもテストは失敗しなくなります。

ただし、単純にこの設定変更を加えるだけだと、全テストにこの変更が適用されてしまいます。そのため、setupメソッドで元の値を保存し、teardownメソッドで元の値に戻すようにしました。

  setup do
    @blog = blogs(:one)
    # 元の値をインスタンス変数に保存する
    @raise_server_errors = Capybara.raise_server_errors
  end

  teardown do
    # 元の値に戻す
    Capybara.raise_server_errors = @raise_server_errors
  end

  test "visiting a Blog" do
    # このテストを実行するときだけ設定を変える
    Capybara.raise_server_errors = false

    visit blog_url(@blog.id + 1)
    assert_text 'ActiveRecord::RecordNotFound'
  end

assert_text 'ActiveRecord::RecordNotFound' の部分はブラウザ上にエラーメッセージが表示されていることを検証しています。

capybara-202203150908357562677473.jpg

参考情報:RSpecの場合

RSpecであれば次のようなテストコードを書きます(rspec-rails 5.0.2で動作確認)。

ドライバとしてrack_testを使っている場合

require 'rails_helper'

RSpec.describe "Blogs", type: :system do
  before do
    driven_by(:rack_test)
  end

  example 'Page not found' do
    blog = Blog.create!(title: 'MyText')

    # visitしたタイミングで例外が発生することを検証する
    expect{ visit blog_url(blog.id + 1) }.to raise_error(ActiveRecord::RecordNotFound)
  end
end

ドライバとしてselenium_webdriverを使っている場合

require 'rails_helper'

RSpec.describe "Blogs", type: :system do
  before do
    driven_by(:selenium_chrome_headless)
  end

  describe '404 error' do
    # aroundフックを使って、Minitestでいうところのsetupとteardownと
    # 設定値の変更を一気に行う
    around do |example|
      original = Capybara.raise_server_errors
      Capybara.raise_server_errors = false
      example.run
      Capybara.raise_server_errors = original
    end

    example 'Page not found' do
      blog = Blog.create!(title: 'MyText')
      visit blog_path(blog.id + 1)
      # ブラウザ上にエラーメッセージが表示されていることを検証する
      expect(page).to have_text 'ActiveRecord::RecordNotFound'
    end
  end
end

結合テスト(IntegrationTest)の場合

結合テストの場合は比較的シンプルにテストが書けます。

require 'test_helper'

class BLogsTest < ActionDispatch::IntegrationTest
  setup do
    @blog = blogs(:one)
  end

  test "visiting a Blog" do
    assert_raise(ActiveRecord::RecordNotFound) { get blog_url(@blog.id + 1) }
  end
end

RSpecでもリクエストスペックで同じように書けると思います(未確認)。

11
1
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
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?