Skip to content

Commit 2605504

Browse files
authored
Improve error message for immutable descriptors (#2664)
* improve error message for immutable descriptors * run prettier
1 parent 6d48f12 commit 2605504

File tree

4 files changed

+83
-5
lines changed

4 files changed

+83
-5
lines changed

docs/faq.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
layout: page
3+
title: Frequently Asked Questions
4+
---
5+
6+
# Frequently Asked Questions
7+
8+
## Property Descriptor Errors
9+
10+
### "Descriptor for property X is non-configurable and non-writable"
11+
12+
If you encounter an error like this:
13+
14+
```
15+
TypeError: Descriptor for property toBeMocked is non-configurable and non-writable
16+
```
17+
18+
This error occurs when Sinon tries to stub or spy on a property that has been defined as immutable by JavaScript's property descriptor system. This is not a bug in Sinon, but rather a limitation imposed by the JavaScript engine itself.
19+
20+
#### Common Causes
21+
22+
1. **ES Module transpilation**: When ES modules are transpiled to CommonJS (e.g., by TypeScript, Babel, or SWC), the exported properties often become non-configurable and non-writable.
23+
24+
2. **Object.freeze() or Object.seal()**: Objects that have been frozen or sealed have immutable properties.
25+
26+
3. **Native browser/Node.js APIs**: Some built-in objects and their properties are inherently immutable.
27+
28+
4. **Third-party libraries**: Some libraries define their exports with non-configurable descriptors.
29+
30+
#### Solutions
31+
32+
1. **Use dependency injection**: Instead of stubbing the import directly, pass the dependency as a parameter:
33+
34+
```javascript
35+
// Instead of this:
36+
import { toBeMocked } from "./module";
37+
sinon.stub(module, "toBeMocked"); // This might fail
38+
39+
// Do this:
40+
function myFunction(dependency = toBeMocked) {
41+
return dependency();
42+
}
43+
44+
// In tests:
45+
const stub = sinon.stub();
46+
myFunction(stub);
47+
```
48+
49+
2. **Stub at the module level**: For ES modules, consider using a tool like `proxyquire` or `testdouble.js` for module-level mocking.
50+
51+
3. **Use dynamic imports**: Dynamic imports can sometimes work around transpilation issues:
52+
53+
```javascript
54+
// In your test
55+
const module = await import("./module");
56+
sinon.stub(module, "toBeMocked");
57+
```
58+
59+
4. **Restructure your code**: Consider whether the code under test can be refactored to be more testable.
60+
61+
#### For TypeScript Users
62+
63+
When using TypeScript with SWC or similar transpilers, see our [TypeScript with SWC guide]({% link _howto/typescript-swc.md %}) for specific solutions.
64+
65+
#### Further Reading
66+
67+
- [MDN: Object.getOwnPropertyDescriptor()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)
68+
- [MDN: Property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Description)
69+
- [How-to: Stub dependencies in CommonJS]({% link _howto/stub-dependency.md %})

lib/sinon/stub.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ function assertValidPropertyDescriptor(descriptor, property) {
136136
}
137137
if (descriptor.isOwn && !descriptor.configurable && !descriptor.writable) {
138138
throw new TypeError(
139-
`Descriptor for property ${property} is non-configurable and non-writable`,
139+
`The descriptor for property \`${property}\` is non-configurable and non-writable. ` +
140+
`Sinon cannot stub properties that are immutable. ` +
141+
`See https://sinonjs.org/faq#property-descriptor-errors for help fixing this issue.`,
140142
);
141143
}
142144
if ((descriptor.get || descriptor.set) && !descriptor.configurable) {

test/issues/issues-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ describe("issues", function () {
780780
const instance =
781781
createInstanceFromClassWithReadOnlyPropertyDescriptor();
782782

783-
// per #2491 this throws 'TypeError: Descriptor for property aMethod is non-configurable and non-writable'
783+
// per #2491 this throws 'TypeError: The descriptor for property `aMethod` is non-configurable and non-writable. Sinon cannot stub properties that are immutable. See https://sinonjs.org/faq#property-descriptor-errors for help fixing this issue.'
784784
// that makes sense for descriptors taken from the object, but not its prototype, as we are free to change
785785
// the latter when setting it
786786
refute.exception(() => {

test/shared-spy-stub-everything-tests.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,16 @@ module.exports = function shared(createSpyOrStub) {
159159
configurable: false,
160160
});
161161

162-
assert.exception(function () {
163-
createSpyOrStub(myObj, "ignoreme");
164-
}, new TypeError("Descriptor for property ignoreme is non-configurable and non-writable"));
162+
assert.exception(
163+
function () {
164+
createSpyOrStub(myObj, "ignoreme");
165+
},
166+
new TypeError(
167+
"The descriptor for property `ignoreme` is non-configurable and non-writable. " +
168+
"Sinon cannot stub properties that are immutable. " +
169+
"See https://sinonjs.org/faq#property-descriptor-errors for help fixing this issue.",
170+
),
171+
);
165172
});
166173

167174
it("throws on accessor property descriptors that are not configurable", function () {

0 commit comments

Comments
 (0)