如何使用Sinon.JS存根已被promisify的方法?修复测试失效问题
Hey there! Let's figure out why your Sinon stub isn't working and fix it up.
The Problem
Your custom.js runs util.promisify(fs.access) the moment the module loads. This creates a Promise-style function that's permanently tied to the original fs.access method. When you stub fs.access in your tests later, that pre-made promisified function doesn't pick up the stub—it still calls the real fs.access, which is why your tests are failing.
Fix 1: Use proxyquire to Replace Dependencies (No Code Changes to custom.js)
proxyquire lets you swap out a module's dependencies when you load it, so we can make sure util.promisify returns our stubbed function instead of wrapping the real fs.access.
First, install it:
npm install proxyquire --save-dev
Then update your test file:
const chai = require('chai') const chaiAsPromised = require('chai-as-promised') const sinon = require('sinon') const proxyquire = require('proxyquire') chai.use(chaiAsPromised) const expect = chai.expect describe('custom', () => { let sandbox = sinon.createSandbox() let custom let stubbedAccess beforeEach(() => { // Create a stubbed Promise-style access function stubbedAccess = sandbox.stub() // Load custom.js and replace util.promisify to return our stub custom = proxyquire('./custom', { util: { promisify: () => stubbedAccess } }) }) afterEach(() => sandbox.restore()) it('returns "File exists: true" when the file is accessible', () => { stubbedAccess.resolves() // Simulate successful access return expect(custom()).to.eventually.equal('File exists: true') }) it('returns "File exists: false" when the file doesn\'t exist', () => { const noFileError = new Error('No such file or directory') noFileError.code = 'ENOENT' stubbedAccess.rejects(noFileError) // Simulate missing file error return expect(custom()).to.eventually.equal('File exists: false') }) it('throws an error for non-ENOENT issues', () => { const permError = new Error('Permission denied') permError.code = 'EACCES' stubbedAccess.rejects(permError) // Simulate permission error return expect(custom()).to.be.rejectedWith(permError) }) })
Fix 2: Refactor custom.js for Dependency Injection
If you don't want to add another library, you can adjust your module to let tests inject a custom access function. This makes your code more testable long-term.
Update custom.js:
const fs = require('fs') const util = require('util') // Default to promisified fs.access, but let tests override it let access = util.promisify(fs.access) // Expose a method to set a custom access function for testing function setAccess(customAccess) { access = customAccess } const custom = () => { return access('myfile', fs.constants.R_OK) .then(() => true) .catch(err => { if (err.code !== 'ENOENT') throw err return false }) .then(fileExists => `File exists: ${fileExists}`) } module.exports = { custom, setAccess }
Then update your tests:
const chai = require('chai') const chaiAsPromised = require('chai-as-promised') const sinon = require('sinon') const { custom, setAccess } = require('./custom') chai.use(chaiAsPromised) const expect = chai.expect describe('custom', () => { let sandbox = sinon.createSandbox() let stubbedAccess beforeEach(() => { stubbedAccess = sandbox.stub() setAccess(stubbedAccess) // Inject our stub into the module }) afterEach(() => sandbox.restore()) it('returns "File exists: true" when the file is accessible', () => { stubbedAccess.resolves() return expect(custom()).to.eventually.equal('File exists: true') }) it('returns "File exists: false" when the file doesn\'t exist', () => { const noFileError = new Error('No such file or directory') noFileError.code = 'ENOENT' stubbedAccess.rejects(noFileError) return expect(custom()).to.eventually.equal('File exists: false') }) })
Both of these approaches will make sure your tests use the stubbed function instead of the real fs.access. The first one keeps your business code untouched, while the second makes your module inherently more testable.
内容的提问来源于stack exchange,提问作者Eduardo

