前回の記事ではPayjpを使った定期購入の実装について解説した。そこで今回は、Payjpを使った実装のテストについて書いてみたいと思う。
Payjpは外部APIなので、API呼び出し部分のモックを作ってテストを記述した方が良い。
対象となるアプリ
2つ前の記事で作成した単発購入機能のテストを書いていきたいと思う。(決して定期実装の方がめんどくさいからではない!!!………多分)
アプリの仕様
もう一度思い出してみる。まずフロントはチェックアウトを置くだけなので、テストの対象外。
<%# app/views/charges/new.html.erb %>
<script
type="text/javascript"
src="https://checkout.pay.jp/"
class="payjp-button"
data-key="#{ENV['PAYJP_PUBLIC_KEY']}"
data-submit-text="購入する"
data-text="カードを入力"
>
</script>
決済履歴を残しておく必要があるので、PaymentHistoryモデルを用意する。
# app/models/payment_history
# == Schema Information
#
# Table name: orders
#
# id :uuid not null, primary key
# amount :integer default(0), not null
# error_detail :string
# error_message :string
# status(決済ステータス) :integer default("before_payment"), not null
# created_at :datetime not null
# updated_at :datetime not null
# charge_id(Payjp決済ID) :string default(""), not null
# user_id :uuid not null
#
# Indexes
#
# index_orders_on_user_id (user_id)
class PaymentHistory
belongs_to :user
enum status: {
before_payment: 0, # 未決済
completed: 1, # 決済完了
failed: 2, # 決済失敗
}
end
コントローラーには決済の処理だけを書いておいた。
# app/controllers/charges_controller.rb
class ChargesController < ApplicationController
PAYJP_ERROR_CODE = {
'invalid_number' => 'カード番号が不正です',
'invalid_cvc' => 'CVCが不正です',
'invalid_expiration_date' => '有効期限年、または月が不正です',
'incorrect_card_data' => 'カード番号、有効期限、CVCのいずれかが不正です',
'invalid_expiry_month' => '有効期限月が不正です',
'invalid_expiry_year' => '有効期限年が不正です',
'expired_card' => '有効期限切れです',
'card_declined' => 'カード会社によって拒否されたカードです',
'processing_error' => '決済ネットワーク上でエラーが発生しました',
'missing_card' => '顧客がカードを保持していない',
'unacceptable_brand' => '対象のカードブランドが許可されていません'
}.freeze
def create
payment_history = current_user.payment_histories.create(status: :before_payment)
response = Payjp::Charge.create(
amount: params[:amount],
card: params[:payjp_token],
currency: 'jpy'
)
payment_history.update!(
status: :completed,
amount: response.amount,
charge_id: response.id
)
render json: { message: '決済完了' }, status: 200
rescue Payjp::PayjpError => e
err = e.json_body[:error]
payment_history.update!(
status: :failed,
error_message: err[:code],
error_detail: PAYJP_ERROR_CODE[err[:code]]
)
render json: { message: PAYJP_ERROR_CODE[err[:code]] }, status: 400
rescue StandardError => e
payment_history.update!(
status: :failed,
error_message: 'failed_payment',
error_detail: '何らかの理由で決済に失敗しました'
)
render json: { message: '何らかの理由で決済に失敗しました' }, status: 500
end
end
Payjpの処理を記述しているのはコントローラーだけなので、Request Specを書いていく。
モックの作成
APIを呼び出さなくても期待した値が返却されるように、モック用のヘルパーを用意する。
# spec/support/payjp_mock_helpers.rb
module PayjpMockHelpers
def charge_mock(payjp_charge_id)
payjp_charge = double("Payjp::Charge")
allow(Payjp::Charge).to receive(:create).and_return(payjp_charge)
allow(payjp_charge).to receive(:id).and_return(payjp_charge_id)
allow(payjp_charge).to receive(:amount).and_return(payjp_charge_amount)
end
end
上記のヘルパーをrails_helper
でincludeする。
# spec/rails_helper.rb
RSpec.configure do |config|
# .......
# .......
# .......
# .......
config.include PayjpMockHelpers
end
モックが用意出来たらRequest Specを書いていく。
# spec/requests/charges_spec.rb
RSpec.describe 'charges', type: :request do
describe 'POST /charges' do
let(:user) { FactoryBot.create(:user) }
let(:payjp_charge_id) { 'ch_xxxxxxxxxxxxxxxxxxxxxxxxxxxx' }
before do
charge_mock(payjp_charge_id)
end
it '決済出来る' do
sign_in user # これはdeviseで用意されているhelper
charge_params = {
amount: 18000,
payjp_token: 'tok_xxxxxxxxxxxxxxxxxxxxxxxx',
}
expect {
post charges_path, params: charge_params, as: :json
}.to change { user.payment_histories.count }.by(1)
expect(response).to have_http_status(200)
end
end
end
以上でテストを記述出来た。
まとめ
Payjpを使った実装のテストを、モックの作り方も含めて解説した。
この記事では以上系のテストは行なっていないし、モックの作り方ももっと良いやり方があるかもしれないので、是非自分で調べてブラッシュアップしてほしい。