GraphQL Schemaから、Swiftコードのモックを自動生成

notion image
 
💡
この記事は、「NEWT Product Advent Calendar 2024」Day16 および「iOS Advent Calendar 2024」Day23の記事となります。
 
こんにちは!「NEWT Product Advent Calendar 2024」16日目は、令和トラベル iOSエンジニアのRickが、EM miisanからバトンをもらい、「GraphQL Schemaから、Swiftコードのモックを自動生成する」について紹介します。ぜひ最後までお楽しみください!
 

はじめに


私が開発する海外旅行予約アプリ「NEWT(ニュート)」では、API SchemaにGraphQLを採用しており、GraphQLクライアントには apollo を利用しています。詳しくはこちらの記事をご覧ください。
 
NEWTのiOSアプリ開発では、GraphQL Schemaから生成されたSwiftの型を、Viewの描画やロジックに直接利用することがよくあります。そのため、Unit TestやXcodePreviewsでのUI確認、さらには開発全般において、APIレスポンスのモックが不可欠です。
 
本記事では、GraphQL Schemaで定義された型をSwiftの型としてモック化する方法について詳しく解説します。
環境(2024/12時点)
  • Xcode 16.1
  • apollo-ios : v1.15.3
  • apollo-ios-codegen : v1.15.3
 
まずは、apollo-iosとapollo-ios-codegenを使用して、Schemaから生成されたSwift型をどのようにモック化できるか、基本的な手段を見ていきましょう。
 

apollo-ios-codegenを利用したモック生成方法


デフォルト設定では、Dictionaryを以下のように定義し、それをData型のInitializerに渡して初期化することになります。
 
Codegen時にOptionでselectionSetInitializersを指定すると、各プロパティを引数に取るInitializerを生成できます。
(ref : Output options
 
しかし、開発を進める上で、実際のUnit Testでは、特定のプロパティにのみ関心を持つケースが多くあります。Xcode Previewsで特定のパターンを確認する際も同様です。このような場合、結果に影響を与えない引数については、値を指定しなくても良い仕組みがあると便利です。
 
これに対して、型安全なモックを作成するための型と機能が提供されています。Codegen時に、configurationでモック用コードの生成先を指定することで実現できます(ref : codegen-configuration#file-input)。
 
生成された型を利用することで、型安全にモックが可能になります。
 
しかし、このMock型はすべてのケースに適用できるわけではありません。では、どのような場合に問題が生じるのでしょうか?

apollo-ios-codegenを使ったモック生成で直面する問題


Fragmentは別の型として生成される

apollo-ios-codegenを使用してモックを生成すると、Mock型の型パラメーターに指定する型が生成され、これらはMockObjectに準拠しています。
 
そのため、Fragmentを受け取るコードでは、モックを使ってもFragment型に置き換える必要があります。
 

Optional unwrappingが必要になる

Mock型のプロパティにアクセスする際は、dynamic member lookupを利用します。MockのInitializerの各引数にはデフォルトでnilが指定されており、nilの可能性があるため、Optionalとして取り出す必要があります。そのため、毎回Optional unwrappingを行う手間がかかります。
 

型パラメーターの指定が面倒

例外もありますが、基本的にはMock型を初期化する際に、型パラメーターを指定する必要があります。
 
また、let a = Mock<Christmas>(のように記述したとしても、Xcodeで引数の補完が効かない場合もあり、型パラメーターに指定する型の中身を確認する必要がありため、手間がかかります。
これらの困りごとを解決する方法を考えたので、次のセクションで紹介します。
 

SourceryでTestDoubleを生成


Sourceryは、stencilテンプレートを使ってSwiftのコードを自動生成するツールで、ボイラープレートコードの自動生成などに広く活用されます。
inputに指定した範囲のSwiftコードを読み取り、その情報に基づいてコードを自動生成します。まずは、今回モックを生成したい型の情報を確認しましょう。
 
モック対象となる型は、以下の2つです。
  • OperationのInnerTypeとして存在するData型
  • Operationが返却するPrimitiveでない型
 
これらの型はすべて、SelectionSetプロトコルまたはInlineFragmentプロトコルに準拠しています。
 
そのため、以下のようなStencilを記述することで、上記の型に対するモックを生成できます。これは、先ほど紹介したselectionSetInitializersオプションで生成したInitializerを利用しています。そうでなければ、プロパティの参照では関係のないものまで含まれてしまうためです。
 
末端に存在するPrimitiveな型・enumを表現するGraphQLEnum<T: EnumType>型に対しては、自前でモックを用意します。
 
結果、以下のようなコードが生成されました 🎉
デフォルト引数には .placeholder() が再起的に指定されており、必要な値だけを指定することができます。
 
apollo-ios-codegenで生成するモック機構の不便さを解消し、関心の対象に集中できる関数を用意できました。
 

他に検討した案


GraphQLのディレクティブを活用し、SchemaからSwiftのコードを生成する際に、一緒にモックを生成することも検討しました。それにより、Sourceryのinstall時間・実行時間を削減できる可能性もあります。しかし、Sourceryに慣れていたこと・将来置き換えが難しくないことを理由に、短期的なスピードを優先し、Sourceryを使った生成方法を採用しました。
 

最後に


いかがでしたか?
Sourceryを使用してGraphQLの型に対するモック生成方法を説明しました。apollo等を利用して、Schemaから型生成を利用している皆さんにとってのアイディアの一つになれば幸いです。
 
NEWTにはまだまだ成長の余地が多分にあります。私のように、旅好きなメンバーが多い会社ではありますが、もちろんそうでない方も大歓迎です! スタートアップの事業グロースにコミットしたい、社会やカスタマーに価値提供できるプロダクトに携わりたい、など令和トラベルにジョインする理由はさまざまですので、少しでもご興味を持っていただけましたら、ぜひ一度カジュアルにお話しさせてください。
 

「NEWT Tech Talk」のお知らせ


令和トラベルでは、技術的な知識や知見・成果を共有するLT会を毎月実施しています。発表テーマや令和トラベルに興味をお持ちいただいた方は、誰でも気軽に参加いただけます。

2025年1月のテーマは、 ” プロダクトマネジメント ”

新年第1弾のNEWT Tech Talkは、「NEWT(ニュート)」のプロダクト開発を牽引するPM 藤沼・Backendエンジニア兼PM 木村の2名が、”カスタマーファーストを実現するプロダクトマネジメントの舞台裏” と題して、LT形式で発表を行います。
 

令和トラベルでは一緒に働く仲間を募集しています


この記事を読んで会社やプロダクトについて興味をお持ちいただけましたら、ご連絡お待ちしています!フランクに話だけでも聞きたいという方は、カジュアル面談も実施できますので、お気軽にお声がけください。
 

今年最後の特大セールも開催中!


NEWTでは、12/26 お昼11:59まで、2024年最後のおトクなセールを開催中です!
 

📣宣伝


最後までお読みいただき、ありがとうございました!
次回の「NEWT Product Advent Calendar 2024」Day17は、EMのyoshikeiが担当します。事業フェーズにあわせた組織開発とマネジメントスタイルの変遷について紹介する予定です。次のブログもお楽しみに!

# iOS

# advent calendar