【Rails】minitestでログインしてる時の処理を書きたい【devise】


前提条件

  • Rails5
  • ログイン処理はdeviseを使用
  • minitest

ログインしてる時のtestを書きたい

今、userがpostを複数持っているとします

user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :delete_all
end
post.rb
class Post < ApplicationRecord
  belongs_to :user
end

この時、「新しいpostの新規作成画面」はログインしているユーザーにしか表示したくありません。

posts_controller.rb
class PostsController < ApplicationController
  # ログインしてないユーザーを弾く
  before_action :authenticate_user!, only: [:new]

  def new
    @post = Post.new
  end
end

ここでtestを普通に書くと

posts_controller_test.rb
require 'test_helper'

class PostsControllerTest < ActionDispatch::IntegrationTest
  def setup
    @post = posts(:one)
    @user = users(:one)
  end

  def test_new
    get new_post_path
    assert_response :success
  end
end
$ rails test test/controllers/posts_controller_test.rb

...
Expected response to be a <2XX: success>, but was a <302: Found> 

ユーザーがログインしてない状態でアクセスしようとしてるので、当然エラーになってしまうんですね。
deviseでログインしてる前提のテストの書き方を調べました。

Devise::TestHelpersはうまくいかない

調べてみると出てくるのがDevise::TestHelpersをincludeするという方法なのですが、これだとエラーが出てうまくいきません。

posts_controller_test.rb
require 'test_helper'

class PostsControllerTest < ActionDispatch::IntegrationTest
  include Devise::TestHelpers #追加

  def setup
    @post = posts(:one)
    @user = users(:one)
  end

  def test_new
    sign_in(@user) #追加
    get new_post_path
    assert_response :success
  end
end

テスト実行すると

$ rails test test/controllers/posts_controller_test.rb

E

Error:
PostsControllerTest#test_new:
NoMethodError: undefined method `env' for nil:NilClass

どうやらこれはPostsControllerTestがActiveSupport::TestCaseを継承してる時に使える方法らしいです。
rails gで生成される標準のtestのファイルの中だと、modelのtestなどはActiveSupport::TestCaseを継承しているのでこの方法が使えます。
ただ、controllerのtestは標準でActionDispatch::IntegrationTestを継承しているので、この方法だとエラーが出ているんですね。

Warden::Test::Helpersを使おう!

posts_controller_test.rb
require 'test_helper'

class PostsControllerTest < ActionDispatch::IntegrationTest
  include Warden::Test::Helpers #追加

  def setup
    @post = posts(:one)
    @user = users(:one)
  end

  def test_new
    login_as(@user, scope: :user) #追加
    get new_post_path
    assert_response :success
  end
end

ActionDispatch::IntegrationTestに関してはこの書き方でうまく行くはずです