【rails】rspecで例外処理をテストする方法
例外処理とは
例外処理は通常のフローでエラーが発生した時にどうするか、ということ。
通常のsaveとかでは以下のようにif節
でsaveできない時を指定できる。
def index @orders = Order.all end def new @order = Order.new end def create if @order.save redirect_to order_index_path else flash[:error] = "正しく入力してください" redirect_to order_new_path end end
ところが、Mailerとかだと基本的に遅れることが前提になっているためエラーの検知が難しい。 そこで、例外処理を入れておく。
例外処理の書き方
例えばOrderModel
でorderをcreateできたらthanksメールを送るとする。
その際のコードはこんな感じ。
class OrdersController < ApplicationController def index @orders = Order.all end def new @order = Order.new end def create if @order.save ::OrderMailer.thanks(@order).deliver redirect_to order_index_path else flash[:error] = "正しく入力してください" redirect_to order_new_path end end end class OrderMailer < ApplicationMailer def thanks(order) ... # ここはメールのオプションとかを記載(今回は省略) end end
ここで、メールを送る動作の時にエラーが起こると本来index
にリダイレクトするところがエラー描写になってしまう。
そこで、ここに例外処理を追加しておく。
def create if @order.save begin ::OrderMailer.thanks(@order).deliver rescue => e # eはエラーの内容を表示。 flash[:error] = "メールの送信に失敗しました。登録は完了しています。" end redirect_to order_index_path else flash[:error] = "正しく入力してください" redirect_to order_new_path end end
上ではメールが送れなかった時にindexページで送信失敗のフラッシュを表示させている。
rescue => e
ではエラーの内容をrescue
内で使えるようにしている。Bugsnagとかでエラー検知している場合はそれを送ることも可能。
例外を起こす
通常ローカル環境でメール送信のエラーを起こすことはできないので、確認のためには強制的に発生させる必要がある。
そこで使うのがraise
コマンド。
def create if @order.save begin raise ::OrderMailer.thanks(@order).deliver rescue => e # eはエラーの内容を表示。 flash[:error] = "メールの送信に失敗しました。登録は完了しています。" end redirect_to order_index_path else flash[:error] = "正しく入力してください" redirect_to order_new_path end end
これでbegin節内で擬似的にエラーを起こすことができる。
rspecで例外処理を起こしたい
さて本題。ローカル環境でエラーを起こせないということはrspecでも通常はメール送信エラーは起こらない。
そこで、強制的にエラーを発生させる必要がある。
RSpec.describe OrdersController, type: :controller do context 'createページに関して' do let(:order) = { FactoryBot.attributes_for(:order) } describe 'thanksメールの送信成功' do before do order_mailer = double("order_mailer") expect(OrderMailer).to receive(:thanks).with(instance_of(Order)).once.and_return(order_mailer) expect(order_mailer).to receive(:deliver).once end post: create expect(response).to redirect_to(order_index_path) end # 例外処理 describe 'thanksメールの送信失敗' do before do order_mailer = double("order_mailer") allow(OrderMailer).to receive(:thanks).and_raise(RuntimeError) end post: create expect(response).to redirect_to(order_index_path) end end end
通常はモックを作ってexpectでreceiveさせるところをallow.toでand_raise
で強制的にraise
コマンドを発生させている。
Mailerで例外処理が発生した場合e.class = RuntimeError
となるので、今回はそれを記載しておいた。
まとめ
今回はメールで例外処理を起こしたが、URLのパラメータなんかはユーザー操作で変えることができてしまうので想定していない挙動を起こすこともあり得る。 そんな時は例外処理で対応を記載しておくとユーザーにエラーページを見せずにページ遷移ができる。