If you're a Frontend Engineer, you've likely been in a situation where you've been required to start implementing a feature before the API that serves the back end portion of that feature exists. Engineers will often turn to mocking to enable parallel development (meaning both the front-end and back-end portions of the feature are developed in parallel).
Mocking however can come with some disadvantages. The first and most obvious is that mocks can drift from the actual implementation, causing them to be unreliable. A second problematic issues is that mocks can often be verbose; With mocks that contain a lot of data, it might be unclear as to what a certain mock response is actually mocking.
The below data is an example of some data you might find in a code-base:
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... ];
The data I work with everyday looks a lot like this. Arrays of orders or some sort of customer focused information, featuring nested values that help populate tables, popups and cards detailed with all sorts of information.
As an engineer tasked with maintaining applications that heavily rely on such mocks, you might ask 'what is this particular object in the response mocking?'. I've often found myself scrolling through hundreds of examples just like the one above, being unsure as to what each objects purpose was.
As I've become more sure of myself as an engineer, I've tasked myself with solving the above issue; What if every mock could more easily display its purpose? What if an engineer only had to write the lines they were intending to mock?
Whilst playing around with some code and a library called Zod, I found the following method called parse, which attempts to validate incoming data against a known type:
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
This was a light-bulb moment; This little example in the Zod documentation was exactly what I had been looking for! If the parse method could accept a value and return it, then if I passed in a value, I would get it back. I also already knew that I could define default values to a Zod schema. What if passing an empty object would return a full object, with its values? Lo and behold it did; I could define default values on a Zod schema, and return the defaults:
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
Now I had a way to generate objects, however it was still not quite what I was looking for. What I really wanted was a way to only write the exact lines I was 'mocking'. A simple solution might look like:
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 userschema>>} // overrides the base object </z.infer>
However this has it's own flaws; What if the value I wish to override is itself an object or array? I'd then have to manually type out each line that was previously required for that feature to continue to work and be mocked as expected, which defeats the purpose of our work in progress solution.
For a long while, this is as far as I had got, until very recently when I had another stab at improving the above. The first step was defining the 'API'; How did I want my users to interact with this functionality?
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... ];
The above API would allow the user to specify a schema of their choice, and then provide the appropriate overrides and return a user object! Of course we'd want to properly account for arrays as well as a single object. For that purpose, a simple type check against the incoming overrides type proved sufficient:
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
The above is effectively the same code as before, however it's now encapsulating the parsing internally so users don't have to manually do so or know detailed information about Zods parsing method. As you might of guessed from reading through if/else statement included, we've also solved the preservation of nested objects and arrays through the use of a recursive builder function that parses each value and returns its default values specified in the Zod schema.
The above is quite a bit to wrap your head around, but the result is that a user can do the following:
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
When providing the preserveNestedDefaults configuration option to the builder, a user can preserve the key value pairs within a nested object or array! This solves the issue of a user overriding a key that is not a primitive type like a string, and is instead a more complex type and retains all the values minus the ones we choose to override.
This has already been quite a read, so let us end with the result of all our hard work. Lets revisit that first mock, and how we could write it with zodObjectBuilder. First lets define our types and our default values, and pass the resulting schema into the 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 userschema>>} // overrides the base object </z.infer>
The above implementation will return a single object using all of the default values! But we can do better than that, we can now (with the help of a few overload definitions and internal parsing) create arrays of objects, perfect for the use case of mocking API responses:
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
The above outputs an array of orders will the full default values, with overridden delivery statuses! Hopefully this demonstrates how the zodObjectBuilder function can minimise the effort required to create a new mock based on a reliable type safe schema.
With that little demonstration we've reached the end of my first article :) I hope you've enjoyed reading this journey of exploration into improving mocking. zodObjectBuilder is still being built, but it's serving my needs well to minimise mocked objects. If you'd like to play around with the current version, you can find it at https://www.npmjs.com/package/@crbroughton/ts-utils which includes the function.
The above is the detailed content of How Ive been trying to improve mocking with Zod. For more information, please follow other related articles on the PHP Chinese website!

The power of the JavaScript framework lies in simplifying development, improving user experience and application performance. When choosing a framework, consider: 1. Project size and complexity, 2. Team experience, 3. Ecosystem and community support.

Introduction I know you may find it strange, what exactly does JavaScript, C and browser have to do? They seem to be unrelated, but in fact, they play a very important role in modern web development. Today we will discuss the close connection between these three. Through this article, you will learn how JavaScript runs in the browser, the role of C in the browser engine, and how they work together to drive rendering and interaction of web pages. We all know the relationship between JavaScript and browser. JavaScript is the core language of front-end development. It runs directly in the browser, making web pages vivid and interesting. Have you ever wondered why JavaScr

Node.js excels at efficient I/O, largely thanks to streams. Streams process data incrementally, avoiding memory overload—ideal for large files, network tasks, and real-time applications. Combining streams with TypeScript's type safety creates a powe

The differences in performance and efficiency between Python and JavaScript are mainly reflected in: 1) As an interpreted language, Python runs slowly but has high development efficiency and is suitable for rapid prototype development; 2) JavaScript is limited to single thread in the browser, but multi-threading and asynchronous I/O can be used to improve performance in Node.js, and both have advantages in actual projects.

JavaScript originated in 1995 and was created by Brandon Ike, and realized the language into C. 1.C language provides high performance and system-level programming capabilities for JavaScript. 2. JavaScript's memory management and performance optimization rely on C language. 3. The cross-platform feature of C language helps JavaScript run efficiently on different operating systems.

JavaScript runs in browsers and Node.js environments and relies on the JavaScript engine to parse and execute code. 1) Generate abstract syntax tree (AST) in the parsing stage; 2) convert AST into bytecode or machine code in the compilation stage; 3) execute the compiled code in the execution stage.

The future trends of Python and JavaScript include: 1. Python will consolidate its position in the fields of scientific computing and AI, 2. JavaScript will promote the development of web technology, 3. Cross-platform development will become a hot topic, and 4. Performance optimization will be the focus. Both will continue to expand application scenarios in their respective fields and make more breakthroughs in performance.

Both Python and JavaScript's choices in development environments are important. 1) Python's development environment includes PyCharm, JupyterNotebook and Anaconda, which are suitable for data science and rapid prototyping. 2) The development environment of JavaScript includes Node.js, VSCode and Webpack, which are suitable for front-end and back-end development. Choosing the right tools according to project needs can improve development efficiency and project success rate.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

Safe Exam Browser
Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

Dreamweaver CS6
Visual web development tools

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.
