Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
外部APIが絡むテストをちょっといい感じに書く/a-little-nice-writing-e...
Search
Masatoshi Moritsuka
February 26, 2025
Programming
34
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
外部APIが絡むテストをちょっといい感じに書く/a-little-nice-writing-external-api-testing
Masatoshi Moritsuka
February 26, 2025
More Decks by Masatoshi Moritsuka
See All by Masatoshi Moritsuka
拙者、『型は欲しいが型は書きたくない』者たちとの和睦を結び、るびぃにおける型の領地安堵を実現せんと欲す者也 #sekigahara01/sekigahara01
sanfrecce_osaka
4
2.3k
Rails の CLI ツールの書き方/writing-rails-cli-tool
sanfrecce_osaka
0
48
Time.zone.parse('dark')/time-zone-parse-dark
sanfrecce_osaka
0
120
gem_rbs_collection へのコントリビュートから始める Ruby の型の世界/contributing-gem-rbs-collection
sanfrecce_osaka
0
600
Rails と人魚の話/rails-and-mermaid
sanfrecce_osaka
0
480
パターンマッチ使ってるかい?(kyobashi.rb)/use-ruby-s-pattern-matching-on-kyobashi-rb
sanfrecce_osaka
0
270
ApplicationController の継承を分割してエラーを減らした話/dividing-application-controller
sanfrecce_osaka
1
410
Input object ではじめる入力値検証/input-value-validation-using-input-object
sanfrecce_osaka
0
600
実例で学ぶRailsアプリケーションデバッグ入門 〜ログインできちゃってました編〜/rails-application-debug-introduction
sanfrecce_osaka
2
930
Other Decks in Programming
See All in Programming
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
110
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
2
560
LLM Plugin for Node-REDの利用方法と開発について
404background
0
170
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
270
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
0
220
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
320
Vite+ Unified Toolchain for the Web
naokihaba
0
280
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
230
Copilot CLI の継戦能力を高める コンテキスト管理
nozomutu
1
1.2k
JJUG CCC 2026 Spring: JSpecify で実現する Kotlin フレンドリーな Java API 設計
ternbusty
1
160
Lessons from Spec-Driven Development
simas
PRO
0
170
並列実装の現場、2ヶ月間実務でAIを使い倒したAIもPCも私も限界が近い
ming_ayami
0
120
Featured
See All Featured
The Cult of Friendly URLs
andyhume
79
6.9k
How GitHub (no longer) Works
holman
316
150k
Designing Powerful Visuals for Engaging Learning
tmiket
1
410
A designer walks into a library…
pauljervisheath
211
24k
Mind Mapping
helmedeiros
PRO
1
240
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
160
We Are The Robots
honzajavorek
0
240
Making Projects Easy
brettharned
120
6.7k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
290
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
360
Optimizing for Happiness
mojombo
378
71k
Transcript
外部API が絡む テストを ちょっといい感じに 書く 森塚 真年(@sanfrecce_osaka) 2025/02/26 Kyobashi.rb#5 #kyobashirb
自己紹介 森塚 真年 GitHub: @sanfrecce-osaka Twitter(X) ・Qiita: @sanfrecce_osaka 趣味: コミュニティ・勉強会
Machida.rb(next: 2/7( 金)) Hirakata.rb(next: 1/26( 日)) 株式会社GMO エンペイ Ruby3.3.6/Rails7.1( 次は3 月末までに 7.2 に上げ るぞ) Node.js v18/Vue.js 3.3/Vuetify 3.4
本編
外部API 絡むテスト
どのように 書いてますか?
stub_request(:post, 'https://example.com/') .to_return_json(body: { id: 1, foo: 'bar', baz: true
})
1 箇所だけなら まだいいけど…
stub_request(:post, 'https://example.com/') .to_return_json(body: { id: 1, foo: 'bar', baz: true
}) # ... stub_request(:post, 'https://example.com/') .to_return_json(body: { id: 1, foo: 'piyo', baz: true }) # ... stub_request(:post, 'https://example.com/') .to_return_json(body: { id: 1, foo: 'bar', baz: false }) .to_return_json(body: { error_code: 'EE01' })
つらい😇
中には
レスポンスが JSON じゃない API
stub_request(:post, 'https://example.com/') .to_return(body: URI.encode_www_form( ID: 1, Foo: 'bar', Baz #
... stub_request(:post, 'https://example.com/') .to_return(body: URI.encode_www_form( ID: 1, Foo: 'piyo', Ba # ... stub_request(:post, 'https://example.com/') .to_return(body: URI.encode_www_form( ID: 1, Foo: 'bar', Baz .to_return(body: URI.encode_www_form( ErrorCode: 'EE01' ))
つらい😇
そこで
FactoryBot
FactoryBot.define do factory :foo_bar_response, class: Hash do id { 1
} foo { 'bar' } bar { true } initialize_with { attributes.stringify_keys } end end
FactoryBot.define do factory :foo_bar_response, class: Hash, traits: %i[success] trait :success
do id { 1 } foo { 'bar' } bar { true } end trait :error do transient do id { nil } foo { nil } bar { nil } end
stub_request(:post, 'https://example.com/') .to_return_json(body: build(:foo_bar_response)) # ... stub_request(:post, 'https://example.com/') .to_return_json(body: build(:foo_bar_response,
foo: 'piyo' } # ... stub_request(:post, 'https://example.com/') .to_return_json(body: build(:foo_bar_response, baz: false }) .to_return_json(body: build(:foo_bar_response, :error, error
FactoryBot.define do factory :foo_bar_response, class: 'String', traits: %i[succe trait :success
do self.ID { 1 } self.Foo { 'bar' } self.Bar { true } end trait :error do transient do self.ID { nil } self.Foo { nil } self.Bar { nil } end
stub_request(:post, 'https://example.com/') .to_return(body: build(:foo_bar_response)) # ... stub_request(:post, 'https://example.com/') .to_return(body: build(:foo_bar_response,
Foo: 'piyo' }) # ... stub_request(:post, 'https://example.com/') .to_return(body: build(:foo_bar_response, Baz: false }) .to_return(body: build(:foo_bar_response, :error, ErrorCode:
文字列じゃなくて Hash が欲しい場合 attributes_for(:foo_bar_response) => { ID: id, Foo: foo,
Bar:
module ExternalApiStubHelper module Hoge module Fuga def mock_hoge_fuga(stub_hoge_fuga: nil, hoge_fuga_respon
hoge_fuga_request = stub_request(:post, 'https://examp stub_hoge_fuga ? stub_hoge_fuga.call(hoge_fuga_request end end end end
# frozen_string_literal: true module ExternalApiStubHelper include Hoge end
vcr
こんなことない? 外部に実際にリクエストしていて CI が遅い どこで呼ばれているかわからない外部 API のリク エスト 最近見つけたやつ: カード情報を消すための
trait
RSpec.describe 'Foo', type: :request do # ちょっとでも外部の API 叩くなら vcr
を true にする # WebMock で stub する前提でもとりあえず true にする describe 'GET xxx', vcr: true do # ... end end
VCR.configure do |c| # ... c.default_cassette_options = { # CI
では cassette を生成しないようにする record: ENV['CI'] ? :none : :new_episodes, # ... } end
ご清聴 ありがとうございました。
None