Home Web Front-end JS Tutorial How I&#ve been trying to improve mocking with Zod

How I&#ve been trying to improve mocking with Zod

Jan 01, 2025 am 01:11 AM

How I

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...
];
Copy after login
Copy after login

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
Copy after login
Copy after login

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
Copy after login
Copy after login

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<typeof UserSchema>>} // overrides the base object
Copy after login
Copy after login

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...
];
Copy after login
Copy after login

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
Copy after login
Copy after login

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
Copy after login
Copy after login

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<typeof UserSchema>>} // overrides the base object
Copy after login
Copy after login

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
Copy after login

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 I&#ve been trying to improve mocking with Zod. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

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

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1659
14
PHP Tutorial
1258
29
C# Tutorial
1232
24
Demystifying JavaScript: What It Does and Why It Matters Demystifying JavaScript: What It Does and Why It Matters Apr 09, 2025 am 12:07 AM

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The Evolution of JavaScript: Current Trends and Future Prospects The Evolution of JavaScript: Current Trends and Future Prospects Apr 10, 2025 am 09:33 AM

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

JavaScript Engines: Comparing Implementations JavaScript Engines: Comparing Implementations Apr 13, 2025 am 12:05 AM

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript: Exploring the Versatility of a Web Language JavaScript: Exploring the Versatility of a Web Language Apr 11, 2025 am 12:01 AM

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

Python vs. JavaScript: The Learning Curve and Ease of Use Python vs. JavaScript: The Learning Curve and Ease of Use Apr 16, 2025 am 12:12 AM

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

How to Build a Multi-Tenant SaaS Application with Next.js (Frontend Integration) How to Build a Multi-Tenant SaaS Application with Next.js (Frontend Integration) Apr 11, 2025 am 08:22 AM

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

From C/C   to JavaScript: How It All Works From C/C to JavaScript: How It All Works Apr 14, 2025 am 12:05 AM

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

Building a Multi-Tenant SaaS Application with Next.js (Backend Integration) Building a Multi-Tenant SaaS Application with Next.js (Backend Integration) Apr 11, 2025 am 08:23 AM

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

See all articles