Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toThrowError fails to compare subclass of Error (ZodError). #7315

Open
6 tasks done
Amorim33 opened this issue Jan 20, 2025 · 3 comments
Open
6 tasks done

toThrowError fails to compare subclass of Error (ZodError). #7315

Amorim33 opened this issue Jan 20, 2025 · 3 comments

Comments

@Amorim33
Copy link

Amorim33 commented Jan 20, 2025

Describe the bug

Description

toThrow and toThrowError are failing to compare a thrown ZodError.

import { z } from 'zod';

it('throws a zod error', () => {
  expect(() => {
    throw new z.ZodError([
      {
        received: 'aa',
        code: 'invalid_enum_value',
        options: ['development', 'production', 'test'],
        path: ['NODE_ENV'],
        message:
          "Invalid enum value. Expected 'development' | 'production' | 'test', received 'aa'",
      },
    ]);
  }).toThrowError(
    new z.ZodError([
      {
        received: 'aa',
        code: 'invalid_enum_value',
        options: ['development', 'production', 'test'],
        path: ['NODE_ENV'],
        message:
          "Invalid enum value. Expected 'development' | 'production' | 'test', received 'aa'",
      },
    ]),
  );
});

The test case above successfuly passes using the vitest version 2.1.8. However it fails with version 3.0.2

v2.1.8
Image

v3.0.2
Image

Additional Context

ZodError is a class that extends Error:
https://github.com/colinhacks/zod/blob/main/src/ZodError.ts#L200

It sets the prototype chain and defines additional methods.
This may be happening with other subclasses of Error

Although, the tests don't pass for Zod, they work fine for a simple CustomError:

it.only('throws a custom error', () => {
  class CustomError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'CustomError';
    }
  }
  expect(() => {
    throw new CustomError('Custom error message');
  }).toThrowError(new CustomError('Custom error message'));
});

Image


I believe it was introduced in this PR: #5876


i'm opening this issue because I use Zod extensively and love Vitest, but this behavior is preventing me from upgrading to the latest version

If this is not a bug, I apologize and would humbly ask for some guidance on how to make this work.
If this aligns with the library's goals, I'd be thrilled to contribute a PR! ✨

Thank you for your attention and for your amazing open-source work! 🙏

Reproduction

  • Install zod
pnpm add zod
  • Install the latest version of vitest
pnpm add vitest
  • Run the code
import { z } from 'zod';

it('throws a zod error', () => {
  expect(() => {
    throw new z.ZodError([
      {
        received: 'aa',
        code: 'invalid_enum_value',
        options: ['development', 'production', 'test'],
        path: ['NODE_ENV'],
        message:
          "Invalid enum value. Expected 'development' | 'production' | 'test', received 'aa'",
      },
    ]);
  }).toThrowError(
    new z.ZodError([
      {
        received: 'aa',
        code: 'invalid_enum_value',
        options: ['development', 'production', 'test'],
        path: ['NODE_ENV'],
        message:
          "Invalid enum value. Expected 'development' | 'production' | 'test', received 'aa'",
      },
    ]),
  );
});

System Info

System:
    OS: Linux 6.12 Fedora Linux 40 (Workstation Edition)
    CPU: (16) x64 AMD Ryzen 7 7800X3D 8-Core Processor
    Memory: 11.43 GB / 30.49 GB
    Container: Yes
    Shell: 3.7.0 - /usr/bin/fish
  Binaries:
    Node: 20.18.1 - /usr/bin/node
    npm: 10.8.2 - /usr/bin/npm
    pnpm: 9.12.1 - ~/.local/share/pnpm/pnpm
    bun: 1.1.38 - ~/.bun/bin/bun
  npmPackages:
    @vitest/coverage-v8: 3.0.2 => 3.0.2 
    @vitest/ui: 3.0.2 => 3.0.2 
    vitest: 3.0.2 => 3.0.2

Used Package Manager

pnpm

Validations

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Jan 21, 2025

I created a stackblitz repro to start with https://stackblitz.com/edit/vitest-dev-vitest-v3sys5ph?file=test%2Frepro.test.ts
Node's assert.deepStrictEqual also fails, so probably there's some hidden property inside somewhere different between the two, but I cannot tell yet.

It would be great if you can check with Zod side to see what's potential issue.

@hi-ogawa
Copy link
Contributor

The issue is because of ZodError.addIssue/addIssues properties.
https://stackblitz.com/edit/vitest-dev-vitest-bruhvcfu?file=test%2Frepro.test.ts

it('vitest', () => {
  console.log({ ...e1 });
  // {
  //   issues: [
  //     {
  //       received: 'aa',
  //       code: 'invalid_enum_value',
  //       options: [Array],
  //       path: [Array],
  //       message: "Invalid enum value. Expected 'development' | 'production' | 'test', received 'aa'"
  //     }
  //   ],
  //   addIssue: [Function (anonymous)],
  //   addIssues: [Function (anonymous)],
  //   name: 'ZodError'
  // }
  expect(e1.addIssue).toEqual(e2.addIssue);
});

I'm not sure if we need to change something on our side since generalizing toThrowError to work more like toEqual is the spirit of the change, which also more aligns with assert.deepStrictEqual.

One workaround is to use custom equality tester to customize ZodError equality to only compare ZodError.issues https://vitest.dev/api/expect.html#expect-addequalitytesters.

A fix possible on Zod side is to make these two properties addIssue/addIssues as non enumerable. https://github.com/colinhacks/zod/blob/f7ad26147ba291cb3fb257545972a8e00e767470/src/ZodError.ts#L294-L300

@Amorim33
Copy link
Author

Thank you very much for your debug and response @hi-ogawa !!

It was extremely helpful! 🙏

I've opened an issue on the Zod side: colinhacks/zod#3950

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants