sinonjs / sinon

Test spies, stubs and mocks for JavaScript.
https://sinonjs.org/
Other
9.63k stars 769 forks source link

stubbing execSync fails with error: Descriptor for property execSync is non-configurable and non-writable #2474

Closed avishaik closed 1 year ago

avishaik commented 1 year ago

Describe the bug Hello team,

There is an issue with sinon version 14.0.1 when stubbing for example execSync from child_process. For example this code:

import * as cProcess from "child_process";

it('return false when string is empty', function() {
         sandbox.stub(cProcess, "execSync");

          const isPort = retrievePid(3001);
          expect(isPort).to.be.false;
        });

For some reason when running the test, the stub always fails with the following error:

TypeError: Descriptor for property execSync is non-configurable and non-writable
      at assertValidPropertyDescriptor (node_modules\sinon\lib\sinon\stub.js:158:15)
      at Function.stub (node_modules\sinon\lib\sinon\stub.js:85:5)
      at Sandbox.stub (node_modules\sinon\lib\sinon\sandbox.js:388:37)
      at Context.<anonymous> (test\utils.spec.ts:25:36)
      at processImmediate (node:internal/timers:466:21)
      at process.topLevelDomainCallback (node:domain:161:15)
      at process.callbackTrampoline (node:internal/async_hooks:128:24)

I downgraded to 12.0.1 and it's working fine.

To Reproduce Steps to reproduce the behavior:

  1. stub execSync method
  2. Run the test

Please use my test example

Expected behavior Expected the stub to work

Context (please complete the following information):

fatso83 commented 1 year ago

Could you please include some actually runnable sample code? Your example is not runnable as is, and I am not convinced that it works fine in Sinon 12, just that it runs (fails stubbing silently).

avishaik commented 1 year ago

You are right, my mistake, in version 12.0.1 I don't get an error, but the stub does not really work, the execSync function runs the original implementation and not the stub.

Please try the following code: index.ts

import {  execSync } from "child_process";

export function execCmd(cmd: string): string {
    try {
        const cmdRes = execSync(cmd).toString().trim();
        console.log(cmdRes);
        return cmdRes;
    } catch (error) {
        //error
    }
}

index.spec.ts

import { expect } from "chai";
import sinon from "sinon";
import * as childProcess from "child_process";
import { execCmd } from "../src/index";

      it('return string of execSync', function() {
          sinon.stub(childProcess, "execSync").returns("test");
          const cmd = execCmd("dir");
          expect(cmd).to.equal("test");
        }); 

I would expect cmd to equal "test", instead I get the "dir" command result.

mantoni commented 1 year ago

You are destructuring the execSync function from child_process. So you hold the function reference directly. Sinon the replaces the function on the module, but the original function doesn't change.

There is no way sinon can make this work. You will have to hold a reference to the module and call child_process.execSync(...) to call the stub.

fatso83 commented 1 year ago

See my comment here: https://stackoverflow.com/a/52591287/200987