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
38
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.6k
Rails の CLI ツールの書き方/writing-rails-cli-tool
sanfrecce_osaka
0
86
Time.zone.parse('dark')/time-zone-parse-dark
sanfrecce_osaka
0
140
gem_rbs_collection へのコントリビュートから始める Ruby の型の世界/contributing-gem-rbs-collection
sanfrecce_osaka
0
640
Rails と人魚の話/rails-and-mermaid
sanfrecce_osaka
0
500
パターンマッチ使ってるかい?(kyobashi.rb)/use-ruby-s-pattern-matching-on-kyobashi-rb
sanfrecce_osaka
0
300
ApplicationController の継承を分割してエラーを減らした話/dividing-application-controller
sanfrecce_osaka
1
420
Input object ではじめる入力値検証/input-value-validation-using-input-object
sanfrecce_osaka
0
620
実例で学ぶRailsアプリケーションデバッグ入門 〜ログインできちゃってました編〜/rails-application-debug-introduction
sanfrecce_osaka
2
940
Other Decks in Programming
See All in Programming
Webエンジニアなのにブラウザの仕組みがわからないので、Pythonで自作してみた
tatsuki12
4
1.7k
業務時間外もAIに働いてもらう話
colorful12
3
10k
Oxlintはいいぞ(続)
yug1224
1
560
コンパウンドプロダクト開発のためのローカルプロセスマネージャー再発明 #layerxgo
izumin5210
0
660
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
920
Kiroで創り、AgentCoreで繋ぐ!AWSで実践する「AI-DLC」から「AIエージェント統合」までの最新地図
licux
2
350
GraphRAGのKnowledge Graphを 直接!見る/View-GraphRAG's-KnowledgeGraph-directly!
tyumugi1113
0
240
PyO3 で既存 Python 評価器を Rust core 化する ー wasm-bindgen でブラウザにも配るための設計
kdash
1
540
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
150
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
440
PHPプロジェクトの結合バランスを可視化する #php_night
kajitack
0
230
From 6 People Classroom Meetup to 100 People Regional Conference / FOSS4G Hiroshima 2026
furukawayasuto
0
120
Featured
See All Featured
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.3k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
How to Think Like a Performance Engineer
csswizardry
28
2.8k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
230
Technical Leadership for Architectural Decision Making
baasie
3
560
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
65k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Navigating Weather and Climate Data
rabernat
0
510
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
230
My Coaching Mixtape
mlcsv
0
310
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