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

[Bug]: Mocking of getters/setters on automatically mocked classes does not work #13140

Closed
staplespeter opened this issue Aug 15, 2022 · 7 comments · Fixed by #13145
Closed

[Bug]: Mocking of getters/setters on automatically mocked classes does not work #13140

staplespeter opened this issue Aug 15, 2022 · 7 comments · Fixed by #13145

Comments

@staplespeter
Copy link
Contributor

staplespeter commented Aug 15, 2022

Version

28.1.3

Steps to reproduce

Follow the instructions in the guide for automatic mocks and getters/setters.
https://jestjs.io/docs/es6-class-mocks#automatic-mock

Expected behavior

Expect the mock to be created without error

Actual behavior

Error "property does not exist"

Additional context

related to #9675

Currently fixing this issue. Will provide a better bug report in due course.

Environment

Ubuntu 20.04
@staplespeter
Copy link
Contributor Author

staplespeter commented Aug 15, 2022

I have defined an class with a getter objectName in a superclass.
I am mocking the subclass.
Jest navigates the object hierarchy for the mock object and tries to get the property descriptor to create the default mock entity for each property.
This last step is done in /jest-mock/build/index.js

ModuleMocker._getSlots(object) {
...
const ownNames = Object.getOwnPropertyNames(object);
...
}

This is what the debugger sees at this point (undefined), which is the same as what the executing code sees.

>object
Datasource {constructor: ƒ, load: ƒ, save: ƒ}
objectName: ƒ objectName() {\n        return this._objectName;\n    }
constructor: class MySqlDatasource
load: …
save: ƒ save(recordsToUpdate, recordsToInsert)
[[Prototype]]: Object

>Object.getOwnPropertyNames(object)
(3) ['constructor', 'load', 'save']
0: 'constructor'
1: 'load'
2: 'save'
length: 3
[[Prototype]]: Array(0)
[[Prototype]]: Object

>Object.getOwnPropertyDescriptor(object, 'objectName')
undefined

And this is what Node outputs directly for the type, rather than for the retrieved prototypes.

>Object.getOwnPropertyDescriptor(Datasource.prototype, 'objectName')
{
  get: [Function: get objectName],
  set: undefined,
  enumerable: false,
  configurable: true
}

So I flattened out the class hierarchy and now the objectName getter is in the subclass and there is no superclass. Now the property is retrievable.

>object
{constructor: ƒ, objectName: <accessor>, load: ƒ, save: ƒ}
objectName: ƒ objectName() {\n        return this._objectName;\n    }
constructor: class MySqlDatasource
load: …
save: ƒ save(recordsToUpdate, recordsToInsert)
[[Prototype]]: Object

>Object.getOwnPropertyNames(object)
(4) ['constructor', 'objectName', 'load', 'save']

>Object.getOwnPropertyDescriptor(object, 'objectName')
{get: ƒ, set: undefined, enumerable: false, configurable: true}

I don't know enough to guess why the descriptor resolves to undefined when further up the prototype hierarchy, e.g. if Jest is obtaining type information in an unusual way, or if the V8 engine doesn't store type information properly. I couldn't see anything other than standard Object methods for retrieving type info in the Jest code.

@staplespeter
Copy link
Contributor Author

So there are several issues with the coding for getter methods (this will likely effect setters too).

Firstly, in /jest-mock/build/index.js the code that should add the named getter method explicitly ignores it if there is a get property defined (which there is of course). This will not effect setter only properties, at this point anyway.

ModuleMocker._getSlots(object) {
...
         //if (propDesc !== undefined || object.__esModule) {  //Tweak
         if ((propDesc !== undefined && !propDesc.get) || object.__esModule) {
            slots.add(prop);
          }
...
}

Once the properties to mock have been retrieved they are identified and the mock implementation assigned.
Tweaking the previous code to allow the inclusion of the named getter property leads to the next problem.
The object definition is attempted to be read directly: component[slot] => undefined.
This leads to getType() returning null and the property being ignored.

function getObjectType(value) {
  return Object.prototype.toString.apply(value).slice(8, -1);
}

function getType(ref) {
  const typeName = getObjectType(ref);
...
}

ModuleMocker.getMetadata(component, _refs) {
...
  const type = getType(component);
...
  const slotMetadata = this.getMetadata(component[slot], refs);
  //if (Object.getOwnPropertyDescriptor(component, slot).get) {           //do this instead
      //const slotMetadata = this.getMetadata(Object.getOwnPropertyDescriptor(component, slot).get, refs);
...
}

If, however, the property descriptor were retrieved and it's get property tested instead then the getter would be identified correctly.
This would need to be done separately for the get and set functions.

>Object.prototype.toString.apply(Object.getOwnPropertyDescriptor(component, 'objectName').get).slice(8, -1)
'Function'

The function still needs successfully mocked so there may be other barriers I haven't reached yet.

It does not seem that the documented feature of mocking getters/setters https://jestjs.io/docs/es6-class-mocks#static-getter-and-setter-methods is implemented at all.

@mrazauskas
Copy link
Contributor

Made some changes but have no access to push.

Did you try opening a Pull Request?

@staplespeter
Copy link
Contributor Author

I have now issued a pull request for this change

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.

@staplespeter
Copy link
Contributor Author

staplespeter commented Oct 11, 2022 via email

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants