From a7950f37c9085af570068e33c4918e2069d29a54 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 21 Dec 2024 23:37:07 +0800 Subject: [PATCH] fix: mock property support symbol (#62) ## Summary by CodeRabbit - **New Features** - Enhanced mocking capabilities with support for Symbol and number properties. - Expanded test coverage for various asynchronous behaviors and error handling scenarios. - **Bug Fixes** - Improved error handling in tests for file reading functions and HTTP requests. - **Documentation** - Updated test descriptions to reflect new functionalities and broader scope. - **Refactor** - Updated type annotations for property parameters to improve type safety. --- package.json | 2 +- src/index.d.ts | 87 ------------------------------------------------- src/index.ts | 34 +++++++++---------- test/mm.test.ts | 26 +++++++++++---- 4 files changed, 38 insertions(+), 111 deletions(-) delete mode 100644 src/index.d.ts diff --git a/package.json b/package.json index 92f6e06..0903e1a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "node": ">= 18.19.0" }, "dependencies": { - "@cnpmjs/muk-prop": "^1.0.0", + "@cnpmjs/muk-prop": "^1.1.0", "is-type-of": "^2.2.0", "thenify": "^3.3.1" }, diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 9991e5e..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,87 +0,0 @@ -declare function mm(target: any, key: string, prop: any): void; - -declare namespace mm { - // export MockMate type for egg-mock; - type MockMate = typeof mm; - - type Request = ( - url: string | RegExp | { url: string; host: string }, - data: any, - headers?: object, - delay?: number - ) => MockMate; - - type RequestError = ( - url: string | RegExp | { url: string; host: string }, - reqError: string | Error, - resError: string | Error, - delay?: number - ) => MockMate; - - /** - * Mock async function error. - */ - function error(mod: any, method: string, error?: string | Error, props?: object, timeout?: number): MockMate; - - /** - * Mock async function error once. - */ - function errorOnce(mod: any, method: string, error?: string | Error, props?: object, timeout?: number): MockMate; - - /** - * mock return callback(null, data). - */ - function data(mod: any, method: string, data: any, timeout?: number): MockMate; - - /** - * mock return data with Symbol.asyncDispose method - */ - function dataWithAsyncDispose(mod: any, method: string, data: object, timeout?: number): MockMate; - - /** - * mock return callback(null, null). - */ - function empty(mod: any, method: string, timeout?: number): MockMate; - - /** - * spy a function - */ - function spy(mod: any, method: string): void; - - /** - * mock return callback(null, data1, data2). - */ - function datas(mod: any, method: string, datas: any, timeout?: number): MockMate; - - /** - * mock function sync throw error - */ - function syncError(mod: any, method: string, error?: string | Error, props?: object): void; - - /** - * mock function sync return data - */ - function syncData(mod: any, method: string, data?: any): void; - - /** - * mock function sync return nothing - */ - function syncEmpty(mod: any, method: string): void; - - /** - * remove all mock effects. - */ - function restore(): MockMate; - - const http: { - request: Request; - requestError: RequestError; - }; - - const https: { - request: Request; - requestError: RequestError; - }; -} - -export = mm; diff --git a/src/index.ts b/src/index.ts index 53884d5..dac9103 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,12 +10,12 @@ import is from 'is-type-of'; // @ts-ignore import thenify from 'thenify'; -function mock(target: any, property: string, value?: any) { +function mock(target: any, property: PropertyKey, value?: any) { value = spyFunction(target, property, value); return muk(target, property, value); } -function spyFunction(target: any, property: string, fn: any) { +function spyFunction(target: any, property: PropertyKey, fn: any) { if (!is.function(fn)) return fn; // support mock with jest.fn() if (fn._isMockFunction && fn.mock) return fn; @@ -32,14 +32,14 @@ function spyFunction(target: any, property: string, fn: any) { fn.called++; const res = Reflect.apply(target, thisArg, args); if (isAsyncLike && !is.promise(res)) { - throw new Error(`Can\'t mock async function to normal function for property "${property}"`); + throw new Error(`Can\'t mock async function to normal function for property "${String(property)}"`); } return res; }, }); } -function isAsyncLikeFunction(target: any, property: string) { +function isAsyncLikeFunction(target: any, property: PropertyKey) { // don't call getter // Object.getOwnPropertyDescriptor can't find getter in prototypes if (typeof target.__lookupGetter__ === 'function' && target.__lookupGetter__(property)) return false; @@ -90,7 +90,7 @@ function _createError(error?: MockError, props?: Record): Error { return error; } -function _mockError(mod: any, method: string, error?: MockError, props?: Record | number, +function _mockError(mod: any, method: string | symbol, error?: MockError, props?: Record | number, timeout?: number | string, once?: boolean) { if (typeof props === 'number') { timeout = props; @@ -140,7 +140,7 @@ function _mockError(mod: any, method: string, error?: MockError, props?: Record< * @param {Object} props, error properties * @param {Number} timeout, mock async callback timeout, default is 0. */ -function mockError(mod: any, method: string, error?: MockError, +function mockError(mod: any, method: string | symbol, error?: MockError, props?: Record | number, timeout?: number) { return _mockError(mod, method, error, props, timeout); @@ -154,7 +154,7 @@ function mockError(mod: any, method: string, error?: MockError, * @param {Object} props, error properties * @param {Number} timeout, mock async callback timeout, default is 0. */ -function errorOnce(mod: any, method: string, error?: MockError, +function errorOnce(mod: any, method: string | symbol, error?: MockError, props?: Record | number, timeout?: number) { return _mockError(mod, method, error, props, timeout, true); @@ -168,7 +168,7 @@ function errorOnce(mod: any, method: string, error?: MockError, * @param {Array} datas, return datas array. * @param {Number} timeout, mock async callback timeout, default is 10. */ -function mockDatas(mod: any, method: string, datas: any[] | any, timeout?: number) { +function mockDatas(mod: any, method: string | symbol, datas: any[] | any, timeout?: number) { if (timeout) { timeout = parseInt(String(timeout), 10); } @@ -208,7 +208,7 @@ function mockDatas(mod: any, method: string, datas: any[] | any, timeout?: numbe * @param {Object} data, return data. * @param {Number} timeout, mock async callback timeout, default is 0. */ -function mockData(mod: any, method: string, data: any, timeout?: number) { +function mockData(mod: any, method: string | symbol, data: any, timeout?: number) { const isGeneratorFunction = is.generatorFunction(mod[method]); const isAsyncFunction = is.asyncFunction(mod[method]); if (isGeneratorFunction || isAsyncFunction) { @@ -217,7 +217,7 @@ function mockData(mod: any, method: string, data: any, timeout?: number) { return mockDatas(mod, method, [ data ], timeout); } -function dataWithAsyncDispose(mod: any, method: string, data: any, timeout?: number) { +function dataWithAsyncDispose(mod: any, method: string | symbol, data: any, timeout?: number) { data = { ...data, async [Symbol.asyncDispose]() { @@ -234,7 +234,7 @@ function dataWithAsyncDispose(mod: any, method: string, data: any, timeout?: num * @param {String} method, mock module object method name. * @param {Number} [timeout], mock async callback timeout, default is 0. */ -function mockEmpty(mod: any, method: string, timeout?: number) { +function mockEmpty(mod: any, method: string | symbol, timeout?: number) { return mockDatas(mod, method, [ null ], timeout); } @@ -243,9 +243,9 @@ function mockEmpty(mod: any, method: string, timeout?: number) { * @param {Object} mod, module object * @param {String} method, mock module object method name. */ -function spy(mod: any, method: string) { +function spy(mod: any, method: string | symbol) { if (typeof mod[method] !== 'function') { - throw new Error(`spy target ${method} is not a function`); + throw new Error(`spy target ${String(method)} is not a function`); } const originalFn = mod[method]; const wrap = function proxy(this: any, ...args: any[]) { @@ -262,7 +262,7 @@ function spy(mod: any, method: string) { * @param {String|Error} error, error string message or error instance. * @param {Object} [props], error properties */ -function syncError(mod: any, method: string, error?: MockError, props?: Record) { +function syncError(mod: any, method: string | symbol, error?: MockError, props?: Record) { error = _createError(error, props); mock(mod, method, () => { throw error; @@ -276,7 +276,7 @@ function syncError(mod: any, method: string, error?: MockError, props?: Record { return data; }); @@ -288,7 +288,7 @@ function syncData(mod: any, method: string, data?: any) { * @param {Object} mod, module object * @param {String} method, mock module object method name. */ -function syncEmpty(mod: any, method: string) { +function syncEmpty(mod: any, method: string | symbol) { return syncData(mod, method); } @@ -581,7 +581,7 @@ function omit(obj: Record, key: string) { /** * mock class method from instance */ -function classMethod(instance: any, property: string, value?: any) { +function classMethod(instance: any, property: PropertyKey, value?: any) { mock(instance.constructor.prototype, property, value); } diff --git a/test/mm.test.ts b/test/mm.test.ts index 4316fa5..5d8a135 100644 --- a/test/mm.test.ts +++ b/test/mm.test.ts @@ -763,15 +763,14 @@ describe('test/mm.test.js', () => { }); }); - describe('mm()', function() { - + describe('mm(), mock()', function() { it('should mock process.env.KEY work', function() { - const orginalEnv = process.env.NODE_ENV; + const originalEnv = process.env.NODE_ENV; mm(process.env, 'NODE_ENV', 'test2'); process.env.NODE_ENV!.should.equal('test2'); mm.restore(); - assert(process.env.NODE_ENV === orginalEnv); + assert(process.env.NODE_ENV === originalEnv); mm(process.env, 'NODE_ENV', 'test2'); process.env.NODE_ENV!.should.equal('test2'); @@ -779,7 +778,22 @@ describe('test/mm.test.js', () => { process.env.NODE_ENV!.should.equal('production'); mm.restore(); - assert(process.env.NODE_ENV === orginalEnv); + assert(process.env.NODE_ENV === originalEnv); + }); + + it('should mock Symbol property work', () => { + const foo = Symbol('foo'); + const data = { [foo]: 'bar' }; + assert.equal(data[foo], 'bar'); + mm(data, foo, 'bar1'); + assert.equal(data[foo], 'bar1'); + }); + + it('should mock number property work', () => { + const data = { 1: 'bar' }; + assert.equal(data[1], 'bar'); + mm(data, 1, 'bar1'); + assert.equal(data[1], 'bar1'); }); it('should mm() just like muk()', function(done) { @@ -830,7 +844,7 @@ describe('test/mm.test.js', () => { }); }); - it('shoud mock function with property', () => { + it('should mock function with property', () => { const NativeDate = Date; const mockNow = function(date: any) { const NewDate = function(...args: any[]) {