jest
jest copied to clipboard
[Bug]: Mocking of getters/setters on automatically mocked classes does not work
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 https://github.com/facebook/jest/issues/9675
Currently fixing this issue. Will provide a better bug report in due course.
Environment
Ubuntu 20.04
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.
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.
I have now issued a pull request for this change
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.
Hi Tom,
Sorry, new to collaborating, had cloned the Facebook/jest repo directly. Have now copied it and will issue a pull request from that when I've finished working through the remaining issues. Thanks for reaching out.
Regards,
Peter
On Tue, 16 Aug 2022, 14:25 Tom Mrazauskas, @.***> wrote:
Made some changes but have no access to push.
Did you try opening a Pull Request https://github.com/facebook/jest/blob/main/CONTRIBUTING.md#workflow-and-pull-requests ?
— Reply to this email directly, view it on GitHub https://github.com/facebook/jest/issues/13140#issuecomment-1216634999, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGJV4SZKVWQYWZU3ICCRRD3VZOJCXANCNFSM56TMP2ZA . You are receiving this because you authored the thread.Message ID: @.***>
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.