Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
外部APIが絡むテストをちょっといい感じに書く/a-little-nice-writing-e...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Masatoshi Moritsuka
February 26, 2025
Programming
41
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.7k
Rails の CLI ツールの書き方/writing-rails-cli-tool
sanfrecce_osaka
0
87
Time.zone.parse('dark')/time-zone-parse-dark
sanfrecce_osaka
0
140
gem_rbs_collection へのコントリビュートから始める Ruby の型の世界/contributing-gem-rbs-collection
sanfrecce_osaka
0
680
Rails と人魚の話/rails-and-mermaid
sanfrecce_osaka
0
510
パターンマッチ使ってるかい?(kyobashi.rb)/use-ruby-s-pattern-matching-on-kyobashi-rb
sanfrecce_osaka
0
310
ApplicationController の継承を分割してエラーを減らした話/dividing-application-controller
sanfrecce_osaka
1
450
Input object ではじめる入力値検証/input-value-validation-using-input-object
sanfrecce_osaka
0
630
実例で学ぶRailsアプリケーションデバッグ入門 〜ログインできちゃってました編〜/rails-application-debug-introduction
sanfrecce_osaka
2
950
Other Decks in Programming
See All in Programming
現場と育てるRailsモデリング 〜Spreadsheetから始めたモデル設計〜
siroemk
2
830
オーケストレーション記述言語 Katari
yukikurage
0
170
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
190
When benchmarks go bad - what I learned from measuring performance wrong
hollycummins
0
240
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
35
17k
Agentic Software Factoryに、すごく賢いIF文を。 / super smart IF statement into the Agentic Software Factory.
rkaga
8
2.3k
Next.js: The final boss of Web Performance
guaca
0
310
Jetpack Compose Mechanisms
skydoves
3
840
dotNET_vulnclicker_intro.pdf
deka_morita
0
150
Kotlinの15年とServer-Side Kotlin
n_takehata
1
160
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
4
730
RAG の “R” を Swift で覗いてみる 〜「意味から探す」検索の仕組み〜
nao_randd
0
170
Featured
See All Featured
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Building AI with AI
inesmontani
PRO
1
1.3k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
Git: the NoSQL Database
bkeepers
PRO
433
67k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
640
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
530
New Earth Scene 8
popppiees
4
2.7k
HDC tutorial
michielstock
2
970
Being A Developer After 40
akosma
92
590k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
430
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