Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
layout: page
title: Frequently Asked Questions
---

# Frequently Asked Questions

## Property Descriptor Errors

### "Descriptor for property X is non-configurable and non-writable"

If you encounter an error like this:

```
TypeError: Descriptor for property toBeMocked is non-configurable and non-writable
```

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.

#### Common Causes

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.

2. **Object.freeze() or Object.seal()**: Objects that have been frozen or sealed have immutable properties.

3. **Native browser/Node.js APIs**: Some built-in objects and their properties are inherently immutable.

4. **Third-party libraries**: Some libraries define their exports with non-configurable descriptors.

#### Solutions

1. **Use dependency injection**: Instead of stubbing the import directly, pass the dependency as a parameter:

```javascript
// Instead of this:
import { toBeMocked } from "./module";
sinon.stub(module, "toBeMocked"); // This might fail

// Do this:
function myFunction(dependency = toBeMocked) {
return dependency();
}

// In tests:
const stub = sinon.stub();
myFunction(stub);
```

2. **Stub at the module level**: For ES modules, consider using a tool like `proxyquire` or `testdouble.js` for module-level mocking.

3. **Use dynamic imports**: Dynamic imports can sometimes work around transpilation issues:

```javascript
// In your test
const module = await import("./module");
sinon.stub(module, "toBeMocked");
```

4. **Restructure your code**: Consider whether the code under test can be refactored to be more testable.

#### For TypeScript Users

When using TypeScript with SWC or similar transpilers, see our [TypeScript with SWC guide]({% link _howto/typescript-swc.md %}) for specific solutions.

#### Further Reading

- [MDN: Object.getOwnPropertyDescriptor()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)
- [MDN: Property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Description)
- [How-to: Stub dependencies in CommonJS]({% link _howto/stub-dependency.md %})
4 changes: 3 additions & 1 deletion lib/sinon/stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ function assertValidPropertyDescriptor(descriptor, property) {
}
if (descriptor.isOwn && !descriptor.configurable && !descriptor.writable) {
throw new TypeError(
`Descriptor for property ${property} is non-configurable and non-writable`,
`The descriptor for property \`${property}\` 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.`,
);
}
if ((descriptor.get || descriptor.set) && !descriptor.configurable) {
Expand Down
2 changes: 1 addition & 1 deletion test/issues/issues-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ describe("issues", function () {
const instance =
createInstanceFromClassWithReadOnlyPropertyDescriptor();

// per #2491 this throws 'TypeError: Descriptor for property aMethod is non-configurable and non-writable'
// 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.'
// that makes sense for descriptors taken from the object, but not its prototype, as we are free to change
// the latter when setting it
refute.exception(() => {
Expand Down
13 changes: 10 additions & 3 deletions test/shared-spy-stub-everything-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,16 @@ module.exports = function shared(createSpyOrStub) {
configurable: false,
});

assert.exception(function () {
createSpyOrStub(myObj, "ignoreme");
}, new TypeError("Descriptor for property ignoreme is non-configurable and non-writable"));
assert.exception(
function () {
createSpyOrStub(myObj, "ignoreme");
},
new TypeError(
"The descriptor for property `ignoreme` 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.",
),
);
});

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