フロントエンド エンジニアであれば、バックエンド部分を提供する API の前に機能の実装を開始する必要がある状況に陥ったことがあるでしょう。機能が存在します。エンジニアは、並行開発を可能にするためにモックを利用することがよくあります (つまり、機能のフロントエンド部分とバックエンド部分の両方が並行して開発されます)。
しかし、モックにはいくつかの欠点が伴う可能性があります。まず最も明白なのは、モックが実際の実装から逸脱し、信頼性が低くなる可能性があることです。 2 番目の問題は、モックが冗長になることが多いことです。大量のデータを含むモックでは、特定のモック応答が実際に何をモックしているのかが不明瞭になる可能性があります。
以下のデータは、コードベースで見つかる可能性のあるデータの例です。
type Order = { orderId: string; customerInfo: CustomerInfo; // omitted these types for brevity orderDate: string; items: OrderItem[]; paymentInfo: PaymentInfo; subtotal: number; shippingCost: number; tax: number; totalAmount: number; status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; trackingNumber: string | null; }; const mockOrders: Order[] = [ { orderId: "ORD-2024-001", customerInfo: { id: "CUST-1234", name: "Alice Johnson", email: "alice.j@email.com", shippingAddress: { street: "123 Pine Street", city: "Portland", state: "OR", zipCode: "97201", country: "USA" } }, orderDate: "2024-03-15T14:30:00Z", items: [ { productId: "PROD-789", name: "Organic Cotton T-Shirt", quantity: 2, pricePerUnit: 29.99, color: "Navy", size: "M" }, { productId: "PROD-456", name: "Recycled Canvas Tote", quantity: 1, pricePerUnit: 35.00, color: "Natural" } ], paymentInfo: { method: "credit_card", status: "completed", transactionId: "TXN-88776655" }, subtotal: 94.98, shippingCost: 5.99, tax: 9.50, totalAmount: 110.47, status: "shipped", trackingNumber: "1Z999AA1234567890" }, // Imagine more objects here, with various values changed... ];
私が毎日扱っているデータは、これによく似ています。注文または顧客に焦点を当てたある種の情報の配列。あらゆる種類の情報が詳細に記載されたテーブル、ポップアップ、カードの入力に役立つネストされた値を特徴とします。
このようなモックに大きく依存するアプリケーションを保守する任務を負ったエンジニアとして、「応答モック内のこの特定のオブジェクトは何ですか?」と疑問に思うかもしれません。私は、各オブジェクトの目的が何なのかよく分からず、上記のような何百もの例をスクロールしていることによく気づきました。
私はエンジニアとしての自分に自信が持てるようになったので、上記の問題を解決するという使命を自分自身に課しました。すべてのモックがその目的をもっと簡単に表示できたらどうなるでしょうか?エンジニアがモックするつもりの行だけを書く必要があるとしたらどうなるでしょうか?
いくつかのコードと Zod というライブラリをいじっているときに、次の parse というメソッドを発見しました。これは、受信データを既知の型と照合して検証しようとします。
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
これは電球のような瞬間でした。 Zod のドキュメントにあるこの小さな例は、まさに私が探していたものです。 parse メソッドが値を受け入れてそれを返すことができる場合、値を渡せばそれが返されます。また、Zod スキーマにデフォルト値を定義できることもすでに知っていました。空のオブジェクトを渡すと、その値を含む完全なオブジェクトが返される場合はどうなるでしょうか?見よ、そのとおりになった。 Zod スキーマにデフォルト値を定義し、そのデフォルト値を返すことができます:
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) // returns a full user object
これでオブジェクトを生成する方法ができましたが、それでも私が探していたものとはまったく異なりました。私が本当に望んでいたのは、私が「嘲笑」していた正確な行だけを書く方法でした。簡単な解決策は次のようになります:
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) const overridenUser = {...user, ...{ name: "My new name", settings: {}, // I would need to write every key:value for settings :( } satisfies Partial<z.infer<typeof UserSchema>>} // overrides the base object
しかし、これには独自の欠陥があります。オーバーライドしたい値自体がオブジェクトまたは配列の場合はどうすればよいでしょうか?その後、その機能が動作し続け、期待どおりにモックされるために以前に必要だった各行を手動で入力する必要があり、これでは進行中のソリューションの目的が損なわれます。
長い間、これが私が得た限界でしたが、つい最近、上記を改善するためにもう一度努力をしました。最初のステップは「API」を定義することでした。ユーザーにこの機能をどのように操作してもらいたいですか?
type Order = { orderId: string; customerInfo: CustomerInfo; // omitted these types for brevity orderDate: string; items: OrderItem[]; paymentInfo: PaymentInfo; subtotal: number; shippingCost: number; tax: number; totalAmount: number; status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; trackingNumber: string | null; }; const mockOrders: Order[] = [ { orderId: "ORD-2024-001", customerInfo: { id: "CUST-1234", name: "Alice Johnson", email: "alice.j@email.com", shippingAddress: { street: "123 Pine Street", city: "Portland", state: "OR", zipCode: "97201", country: "USA" } }, orderDate: "2024-03-15T14:30:00Z", items: [ { productId: "PROD-789", name: "Organic Cotton T-Shirt", quantity: 2, pricePerUnit: 29.99, color: "Navy", size: "M" }, { productId: "PROD-456", name: "Recycled Canvas Tote", quantity: 1, pricePerUnit: 35.00, color: "Natural" } ], paymentInfo: { method: "credit_card", status: "completed", transactionId: "TXN-88776655" }, subtotal: 94.98, shippingCost: 5.99, tax: 9.50, totalAmount: 110.47, status: "shipped", trackingNumber: "1Z999AA1234567890" }, // Imagine more objects here, with various values changed... ];
上記の API を使用すると、ユーザーは選択したスキーマを指定し、適切なオーバーライドを提供してユーザー オブジェクトを返すことができます。もちろん、単一のオブジェクトだけでなく配列も適切に考慮したいと考えます。そのためには、受信したオーバーライド型に対する単純な型チェックで十分であることが証明されました。
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
上記は実質的に以前と同じコードですが、内部で解析がカプセル化されているため、ユーザーは手動で解析を行ったり、Zods 解析方法に関する詳細情報を知る必要がありません。含まれている if/else ステートメントを読んで推測できるように、各値を解析して Zod スキーマで指定されたデフォルト値を返す再帰ビルダー関数を使用することで、ネストされたオブジェクトと配列の保存も解決しました。
上記はかなり複雑ですが、その結果、ユーザーは次のことができるようになります。
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) // returns a full user object
preserveNestedDefaults 構成オプションをビルダーに提供すると、ユーザーはネストされたオブジェクトまたは配列内にキーと値のペアを保存できます。これにより、文字列のようなプリミティブ型ではなく、より複雑な型であるキーをユーザーがオーバーライドする問題が解決され、オーバーライドすることを選択した値を除いたすべての値が保持されます。
これはすでにかなり読まれているので、私たちのすべての努力の結果で終わりにしましょう。最初のモックと、それを zodObjectBuilder で作成する方法をもう一度見てみましょう。まず、タイプとデフォルト値を定義し、結果のスキーマを zodObjectBuilder に渡します:
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) const overridenUser = {...user, ...{ name: "My new name", settings: {}, // I would need to write every key:value for settings :( } satisfies Partial<z.infer<typeof UserSchema>>} // overrides the base object
上記の実装は、すべてのデフォルト値を使用して単一のオブジェクトを返します。しかし、それよりも優れた方法があり、(いくつかのオーバーロード定義と内部解析を利用して) API 応答をモックするユースケースに最適なオブジェクトの配列を作成できるようになりました。
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = zodObjectBuilder({ schema: UserSchema, overrides: { name: 'My new name', settings: { theme: 'dark' } } // setting is missing the notifications theme :( }); // returns a full user object with the overrides
上記の出力では、配送ステータスが上書きされた、完全なデフォルト値の注文の配列が出力されます。これで、zodObjectBuilder 関数が、信頼できるタイプ セーフ スキーマに基づいて新しいモックを作成するのに必要な労力を最小限に抑えることができることがわかると幸いです。
この小さなデモンストレーションで、最初の記事の終わりに達しました :) モックを改善するためのこの探索の旅を楽しんで読んでいただければ幸いです。 zodObjectBuilder はまだ構築中ですが、モック化されたオブジェクトを最小限に抑えるという私のニーズをうまく満たしています。現在のバージョンを試してみたい場合は、この機能が含まれている https://www.npmjs.com/package/@crbroughton/ts-utils で見つけることができます。
以上がZod を使ってモックを改善しようとしてきた方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。