Skip to content

Add error serializer #3655

@lukaselmer

Description

@lukaselmer

Clear and concise description of the problem

Custom errors with additional attributes are hard to test using toThrowErrorMatchingInlineSnapshot

Example:

class ErrorWithDetails extends Error {
  readonly details: unknown

  constructor(message: string, options: ErrorOptions & { details: unknown }) {
    super(message, options)
    this.details = options.details
  }
}

describe('ErrorWithDetails', () => {
  it('should throw an error with details', () => {
    expect(() => {
      throw new ErrorWithDetails('Example', { details: 'interesting detail' })
    }).toThrowErrorMatchingInlineSnapshot('"Example"') // 'interesting detail' is lost
  })
})

Suggested solution

Add an addErrorSerializer which works like addSnapshotSerializer, but for errors.

Example:

expect.addErrorSerializer({
  serialize(val, config, indentation, depth, refs, printer) {
    const error = val as ErrorWithDetails
    return `${error.message}: ${printer(error.details, config, indentation, depth, refs)}`
  },
  test(val) {
    return val && val instanceof ErrorWithDetails
  },
})

which could then result in:

class ErrorWithDetails extends Error {
  readonly details: unknown

  constructor(message: string, options: ErrorOptions & { details: unknown }) {
    super(message, options)
    this.details = options.details
  }
}

describe('ErrorWithDetails', () => {
  it('should throw an error with details', () => {
    expect(() => {
      throw new ErrorWithDetails('Example', { details: 'interesting detail' })
    }).toThrowErrorMatchingInlineSnapshot('"Example: interesting detail"')
  })
})

Alternative

We can work around that issue, but it is cumbersome:

class ErrorWithDetails extends Error {
  readonly details: unknown

  constructor(message: string, options: ErrorOptions & { details: unknown }) {
    super(message, options)
    this.details = options.details
  }
}

describe('ErrorWithDetails', () => {
  it('should throw an error with details', () => {
    expect(
      catchAndSerializeError(() => {
        throw new ErrorWithDetails('Example', { details: 'interesting detail' })
      })
    ).toMatchInlineSnapshot('"Example \\"interesting detail\\""')
  })
})

function catchAndSerializeError(fn: () => any) {
  try {
    fn()
    throw new Error('Expected an error')
  } catch (error) {
    if (error instanceof ErrorWithDetails) {
      return `${error.message} ${JSON.stringify(error.details)}`
    }
    throw new Error('Expected an ErrorWithDetails')
  }
}

Additional context

No response

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions