Upgrade to Pro — share decks privately, control downloads, hide ads and more …

外部APIが絡むテストをちょっといい感じに書く/a-little-nice-writing-e...

 外部APIが絡むテストをちょっといい感じに書く/a-little-nice-writing-external-api-testing

Avatar for Masatoshi Moritsuka

Masatoshi Moritsuka

February 26, 2025
Tweet

More Decks by Masatoshi Moritsuka

Other Decks in Programming

Transcript

  1. 自己紹介 森塚 真年 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
  2. 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' })
  3. 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' ))
  4. FactoryBot.define do factory :foo_bar_response, class: Hash do id { 1

    } foo { 'bar' } bar { true } initialize_with { attributes.stringify_keys } end end
  5. 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
  6. 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
  7. 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
  8. 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:
  9. 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
  10. vcr

  11. RSpec.describe 'Foo', type: :request do # ちょっとでも外部の API 叩くなら vcr

    を true にする # WebMock で stub する前提でもとりあえず true にする describe 'GET xxx', vcr: true do # ... end end
  12. VCR.configure do |c| # ... c.default_cassette_options = { # CI

    では cassette を生成しないようにする record: ENV['CI'] ? :none : :new_episodes, # ... } end