使用Chai与Chai-Spies在同文件中Mock函数测试依赖函数
I've defined two async functions (
testandtest2) in the same file, wheretest2callstest. I want to mock thetestfunction to test the business logic oftest2. Here's my code:async function test(data) { try { const result = await access_to_data_base(data); return result; } catch (e) { return e; } } async function test2(data) { try { const testResult = await test(data); // 处理获取到的数据 const result = treated_data(); return result; } catch (e) { return e; } } module.exports = { test, test2 }How can I achieve this testing requirement?
Great question! The tricky part here is that test2 calls the internal test function directly, not the exported version—so just mocking the exported test won't work out of the box. Let's walk through a few reliable solutions using common Node.js testing tools like Jest and Rewire.
Solution 1: Adjust Your Module to Use Exported References
The simplest fix (if you're open to modifying your source code) is to have test2 call the exported test function instead of the internal one. This makes mocking straightforward since we can override the exported value:
Updated Source Code
async function test(data) { try { const result = await access_to_data_base(data); return result; } catch (e) { return e; } } async function test2(data) { try { // Use module.exports.test instead of the internal test const testResult = await module.exports.test(data); const result = treated_data(); return result; } catch (e) { return e; } } module.exports = { test, test2 }
Jest Test Code
const { test, test2 } = require('./your-module-file'); describe('test2', () => { // Clear mock between tests beforeEach(() => { jest.clearAllMocks(); }); it('processes data correctly when test succeeds', async () => { // Mock the exported test function to resolve with dummy data jest.spyOn(module.exports, 'test').mockResolvedValue('mocked DB result'); // If you need to mock treated_data too, export it first and spy on it const treatedDataMock = jest.spyOn(module.exports, 'treated_data').mockReturnValue('processed output'); const result = await test2('sample input'); // Verify test was called with the right argument expect(test).toHaveBeenCalledTimes(1); expect(test).toHaveBeenCalledWith('sample input'); // Verify the processing logic ran and returned the expected value expect(treatedDataMock).toHaveBeenCalledTimes(1); expect(result).toBe('processed output'); // Clean up the mock for treated_data treatedDataMock.mockRestore(); }); it('returns errors when test fails', async () => { const mockError = new Error('DB connection failed'); jest.spyOn(module.exports, 'test').mockRejectedValue(mockError); const result = await test2('bad input'); expect(test).toHaveBeenCalledWith('bad input'); expect(result).toEqual(mockError); }); });
Solution 2: Use Rewire to Modify Internal Functions (No Source Code Changes)
If you don't want to adjust your source code, use the rewire package to access and override the internal test function directly.
Step 1: Install Rewire
npm install rewire --save-dev
Step 2: Write the Test with Rewire + Sinon (or Jest)
Here's an example using Sinon for stubbing (works similarly with Jest):
const rewire = require('rewire'); const sinon = require('sinon'); const yourModule = rewire('./your-module-file'); describe('test2', () => { let testStub; // Set up the mock before each test beforeEach(() => { testStub = sinon.stub(); // Replace the internal test function with our stub yourModule.__set__('test', testStub); }); // Restore the original function after each test afterEach(() => { yourModule.__restore__(); }); it('calls test and processes the result', async () => { // Mock test to resolve with dummy data testStub.resolves('mocked DB response'); // Mock the internal treated_data function const treatedDataStub = sinon.stub().returns('final processed data'); yourModule.__set__('treated_data', treatedDataStub); const result = await yourModule.test2('test data'); // Verify test was called correctly sinon.assert.calledOnce(testStub); sinon.assert.calledWith(testStub, 'test data'); // Verify processing ran sinon.assert.calledOnce(treatedDataStub); expect(result).toBe('final processed data'); }); it('handles test failures gracefully', async () => { const testError = new Error('Query failed'); testStub.rejects(testError); const result = await yourModule.test2('bad data'); sinon.assert.calledOnce(testStub); expect(result).toEqual(testError); }); });
Solution 3: Extract test to a Separate Module
For long-term maintainability, consider moving the test function to its own module. This makes mocking trivial since you can just mock the entire module using Jest or any testing framework:
File 1: test.js
async function test(data) { try { const result = await access_to_data_base(data); return result; } catch (e) { return e; } } module.exports = test;
File 2: test2.js
const test = require('./test'); async function test2(data) { try { const testResult = await test(data); const result = treated_data(); return result; } catch (e) { return e; } } module.exports = test2;
Jest Test Code
const test2 = require('./test2'); const test = require('./test'); // Mock the entire test module jest.mock('./test'); describe('test2', () => { beforeEach(() => { jest.clearAllMocks(); }); it('processes data on test success', async () => { test.mockResolvedValue('mocked data'); const treatedDataMock = jest.spyOn(module.exports, 'treated_data').mockReturnValue('processed'); const result = await test2('input'); expect(test).toHaveBeenCalledWith('input'); expect(result).toBe('processed'); }); });
Each approach has its tradeoffs: adjusting your source code is simple, Rewire avoids code changes, and extracting to a module improves testability long-term. Pick the one that fits your project's needs!
内容的提问来源于stack exchange,提问作者Soufiane AIT AKKACHE

