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

Add Jest matcher support for class objects with private fields #6

Open
mttschltz opened this issue Jan 15, 2022 · 0 comments
Open

Add Jest matcher support for class objects with private fields #6

mttschltz opened this issue Jan 15, 2022 · 0 comments
Labels
development experience Frictions or improvements for developing on this project

Comments

@mttschltz
Copy link
Owner

mttschltz commented Jan 15, 2022

Problem

The default .toEqual matcher tries to access private fields and fails, making it unusable for testing objects created from classes.

          const categoryResult = newCategory('id', {
            ...createDetails,
          })
          expect(categoryResult.value).toEqual({
            id: 'id',
            path: 'path',
            name: 'name',
            description: undefined,
            shortDescription: 'short description',
            parent: undefined,
            children: [],
            updated,
          } as Category)

          // TypeError: Cannot read private member #id from an object whose class did not declare it

The suggested workaround in this issue is to use .toMatchObject, but this passes when the received value has additional properties not in the expected value: jestjs/jest#10167 . IMO this makes it no real better than checking all properties manually, as the introduction of a new property will not cause tests to fail (as a reminder for them to be updated).

Possible solution A

Check the above issue again, or search other issues, for another solution!

Possible solution B

A custom matcher could solve this. My thoughts were:

  1. Convert the class object to a plain object
  2. Check for equality using Jest's helper this.equals function: https://jestjs.io/docs/expect#thisequalsa-b
  3. Return a pretty print of the diff: https://jestjs.io/docs/next/jest-platform#jest-diff

I got part of the way through step 1 with the following:

function isClass(v: any): boolean {
  return (
    typeof v === 'object' &&
    Object.keys(Object.getOwnPropertyDescriptors(v.__proto__)).some((k) => k === 'constructor') &&
    !(v instanceof Error || v instanceof Date || v instanceof Array)
  )
}

function toEqualValues(impl: unknown): unknown {
  const entries = Object.entries(Object.getOwnPropertyDescriptors(impl.__proto__)).filter(
    ([k, v]) => k !== 'constructor' && (!v.value || typeof v.value !== 'function'),
  )
  const obj = entries.reduce((o, [k]) => {
    let v = impl[k]
    if (isClass(v)) {
      v = toEqualValues(v)
    }
    if (v instanceof Array) {
      v = v.map((i) => (isClass(i) ? toEqualValues(i) : i))
    }
    o[k] = v
    return o
  }, {})
  return obj
}

It works with nested class objects and class objects in an array property. But fails when a property is a plain object, which sometimes happens in tests where a relation may be created directly using an interface rather than using a factory function that uses a class. E.g.:

        const parent: Category = {
          id: 'parent id',
          children: [],
          name: 'parent name',
          path: 'parent path',
          shortDescription: 'parent short description',
          updated: new Date(),
        }
        const categoryResult = newCategory('id', {
          ...createDetails,
          parent, // not created using a class
          children,
          description: 'description',
        })

This is because Object.getOwnPropertyDescriptors(impl.__proto__) does not seem to return normal properties (i.e. simple properties, not getters/setters/etc).

I didn't feel it was worth continuing past this point but maybe it's not much more effort to get it working

@mttschltz mttschltz added the development experience Frictions or improvements for developing on this project label Jan 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
development experience Frictions or improvements for developing on this project
Projects
None yet
Development

No branches or pull requests

1 participant