| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 286acaa commit 55e4140
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ const { | |||
| 6 | 6 | FunctionPrototypeCall, | |
| 7 | 7 | ObjectDefineProperty, | |
| 8 | 8 | ObjectGetOwnPropertyDescriptor, | |
| 9 | + ObjectGetPrototypeOf, | ||
| 9 | 10 | Proxy, | |
| 10 | 11 | ReflectApply, | |
| 11 | 12 | ReflectConstruct, | |
@@ -130,13 +131,15 @@ class MockTracker { | |||
| 130 | 131 | } | |
| 131 | 132 | ||
| 132 | 133 | method( | |
| 133 | - object, | ||
| 134 | + objectOrFunction, | ||
| 134 | 135 | methodName, | |
| 135 | 136 | implementation = kDefaultFunction, | |
| 136 | 137 | options = kEmptyObject, | |
| 137 | 138 | ) { | |
| 138 | - validateObject(object, 'object'); | ||
| 139 | 139 | validateStringOrSymbol(methodName, 'methodName'); | |
| 140 | + if (typeof objectOrFunction !== 'function') { | ||
| 141 | + validateObject(objectOrFunction, 'object'); | ||
| 142 | + } | ||
| 140 | 143 | ||
| 141 | 144 | if (implementation !== null && typeof implementation === 'object') { | |
| 142 | 145 | options = implementation; | |
@@ -161,8 +164,8 @@ class MockTracker { | |||
| 161 | 164 | 'options.setter', setter, "cannot be used with 'options.getter'" | |
| 162 | 165 | ); | |
| 163 | 166 | } | |
| 167 | + const descriptor = findMethodOnPrototypeChain(objectOrFunction, methodName); | ||
| 164 | 168 | ||
| 165 | - const descriptor = ObjectGetOwnPropertyDescriptor(object, methodName); | ||
| 166 | 169 | let original; | |
| 167 | 170 | ||
| 168 | 171 | if (getter) { | |
@@ -179,7 +182,7 @@ class MockTracker { | |||
| 179 | 182 | ); | |
| 180 | 183 | } | |
| 181 | 184 | ||
| 182 | - const restore = { descriptor, object, methodName }; | ||
| 185 | + const restore = { descriptor, object: objectOrFunction, methodName }; | ||
| 183 | 186 | const impl = implementation === kDefaultFunction ? | |
| 184 | 187 | original : implementation; | |
| 185 | 188 | const ctx = new MockFunctionContext(impl, restore, times); | |
@@ -201,7 +204,7 @@ class MockTracker { | |||
| 201 | 204 | mockDescriptor.value = mock; | |
| 202 | 205 | } | |
| 203 | 206 | ||
| 204 | - ObjectDefineProperty(object, methodName, mockDescriptor); | ||
| 207 | + ObjectDefineProperty(objectOrFunction, methodName, mockDescriptor); | ||
| 205 | 208 | ||
| 206 | 209 | return mock; | |
| 207 | 210 | } | |
@@ -350,4 +353,21 @@ function validateTimes(value, name) { | |||
| 350 | 353 | validateInteger(value, name, 1); | |
| 351 | 354 | } | |
| 352 | 355 | ||
| 356 | + function findMethodOnPrototypeChain(instance, methodName) { | ||
| 357 | + let host = instance; | ||
| 358 | + let descriptor; | ||
| 359 | + | ||
| 360 | + while (host !== null) { | ||
| 361 | + descriptor = ObjectGetOwnPropertyDescriptor(host, methodName); | ||
| 362 | + | ||
| 363 | + if (descriptor) { | ||
| 364 | + break; | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + host = ObjectGetPrototypeOf(host); | ||
| 368 | + } | ||
| 369 | + | ||
| 370 | + return descriptor; | ||
| 371 | + } | ||
| 372 | + | ||
| 353 | 373 | module.exports = { MockTracker }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,6 @@ | |||
| 2 | 2 | const common = require('../common'); | |
| 3 | 3 | const assert = require('node:assert'); | |
| 4 | 4 | const { mock, test } = require('node:test'); | |
| 5 | - | ||
| 6 | 5 | test('spies on a function', (t) => { | |
| 7 | 6 | const sum = t.mock.fn((arg1, arg2) => { | |
| 8 | 7 | return arg1 + arg2; | |
@@ -319,6 +318,157 @@ test('spy functions can be bound', (t) => { | |||
| 319 | 318 | assert.strictEqual(sum.bind(0)(2, 11), 13); | |
| 320 | 319 | }); | |
| 321 | 320 | ||
| 321 | + test('mocks prototype methods on an instance', async (t) => { | ||
| 322 | + class Runner { | ||
| 323 | + async someTask(msg) { | ||
| 324 | + return Promise.resolve(msg); | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + async method(msg) { | ||
| 328 | + await this.someTask(msg); | ||
| 329 | + return msg; | ||
| 330 | + } | ||
| 331 | + } | ||
| 332 | + const msg = 'ok'; | ||
| 333 | + const obj = new Runner(); | ||
| 334 | + assert.strictEqual(await obj.method(msg), msg); | ||
| 335 | + | ||
| 336 | + t.mock.method(obj, obj.someTask.name); | ||
| 337 | + assert.strictEqual(obj.someTask.mock.calls.length, 0); | ||
| 338 | + | ||
| 339 | + assert.strictEqual(await obj.method(msg), msg); | ||
| 340 | + | ||
| 341 | + const call = obj.someTask.mock.calls[0]; | ||
| 342 | + | ||
| 343 | + assert.deepStrictEqual(call.arguments, [msg]); | ||
| 344 | + assert.strictEqual(await call.result, msg); | ||
| 345 | + assert.strictEqual(call.target, undefined); | ||
| 346 | + assert.strictEqual(call.this, obj); | ||
| 347 | + | ||
| 348 | + const obj2 = new Runner(); | ||
| 349 | + // Ensure that a brand new instance is not mocked | ||
| 350 | + assert.strictEqual( | ||
| 351 | + obj2.someTask.mock, | ||
| 352 | + undefined | ||
| 353 | + ); | ||
| 354 | + | ||
| 355 | + assert.strictEqual(obj.someTask.mock.restore(), undefined); | ||
| 356 | + assert.strictEqual(await obj.method(msg), msg); | ||
| 357 | + assert.strictEqual(obj.someTask.mock, undefined); | ||
| 358 | + assert.strictEqual(Runner.prototype.someTask.mock, undefined); | ||
| 359 | + }); | ||
| 360 | + | ||
| 361 | + test('spies on async static class methods', async (t) => { | ||
| 362 | + class Runner { | ||
| 363 | + static async someTask(msg) { | ||
| 364 | + return Promise.resolve(msg); | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + static async method(msg) { | ||
| 368 | + await this.someTask(msg); | ||
| 369 | + return msg; | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + const msg = 'ok'; | ||
| 373 | + assert.strictEqual(await Runner.method(msg), msg); | ||
| 374 | + | ||
| 375 | + t.mock.method(Runner, Runner.someTask.name); | ||
| 376 | + assert.strictEqual(Runner.someTask.mock.calls.length, 0); | ||
| 377 | + | ||
| 378 | + assert.strictEqual(await Runner.method(msg), msg); | ||
| 379 | + | ||
| 380 | + const call = Runner.someTask.mock.calls[0]; | ||
| 381 | + | ||
| 382 | + assert.deepStrictEqual(call.arguments, [msg]); | ||
| 383 | + assert.strictEqual(await call.result, msg); | ||
| 384 | + assert.strictEqual(call.target, undefined); | ||
| 385 | + assert.strictEqual(call.this, Runner); | ||
| 386 | + | ||
| 387 | + assert.strictEqual(Runner.someTask.mock.restore(), undefined); | ||
| 388 | + assert.strictEqual(await Runner.method(msg), msg); | ||
| 389 | + assert.strictEqual(Runner.someTask.mock, undefined); | ||
| 390 | + assert.strictEqual(Runner.prototype.someTask, undefined); | ||
| 391 | + | ||
| 392 | + }); | ||
| 393 | + | ||
| 394 | + test('given null to a mock.method it throws a invalid argument error', (t) => { | ||
| 395 | + assert.throws(() => t.mock.method(null, {}), { code: 'ERR_INVALID_ARG_TYPE' }); | ||
| 396 | + }); | ||
| 397 | + | ||
| 398 | + test('it should throw given an inexistent property on a object instance', (t) => { | ||
| 399 | + assert.throws(() => t.mock.method({ abc: 0 }, 'non-existent'), { | ||
| 400 | + code: 'ERR_INVALID_ARG_VALUE' | ||
| 401 | + }); | ||
| 402 | + }); | ||
| 403 | + | ||
| 404 | + test('spy functions can be used on classes inheritance', (t) => { | ||
| 405 | + // Makes sure that having a null-prototype doesn't throw our system off | ||
| 406 | + class A extends null { | ||
| 407 | + static someTask(msg) { | ||
| 408 | + return msg; | ||
| 409 | + } | ||
| 410 | + static method(msg) { | ||
| 411 | + return this.someTask(msg); | ||
| 412 | + } | ||
| 413 | + } | ||
| 414 | + class B extends A {} | ||
| 415 | + class C extends B {} | ||
| 416 | + | ||
| 417 | + const msg = 'ok'; | ||
| 418 | + assert.strictEqual(C.method(msg), msg); | ||
| 419 | + | ||
| 420 | + t.mock.method(C, C.someTask.name); | ||
| 421 | + assert.strictEqual(C.someTask.mock.calls.length, 0); | ||
| 422 | + | ||
| 423 | + assert.strictEqual(C.method(msg), msg); | ||
| 424 | + | ||
| 425 | + const call = C.someTask.mock.calls[0]; | ||
| 426 | + | ||
| 427 | + assert.deepStrictEqual(call.arguments, [msg]); | ||
| 428 | + assert.strictEqual(call.result, msg); | ||
| 429 | + assert.strictEqual(call.target, undefined); | ||
| 430 | + assert.strictEqual(call.this, C); | ||
| 431 | + | ||
| 432 | + assert.strictEqual(C.someTask.mock.restore(), undefined); | ||
| 433 | + assert.strictEqual(C.method(msg), msg); | ||
| 434 | + assert.strictEqual(C.someTask.mock, undefined); | ||
| 435 | + }); | ||
| 436 | + | ||
| 437 | + test('spy functions don\'t affect the prototype chain', (t) => { | ||
| 438 | + | ||
| 439 | + class A { | ||
| 440 | + static someTask(msg) { | ||
| 441 | + return msg; | ||
| 442 | + } | ||
| 443 | + } | ||
| 444 | + class B extends A {} | ||
| 445 | + class C extends B {} | ||
| 446 | + | ||
| 447 | + const msg = 'ok'; | ||
| 448 | + | ||
| 449 | + const ABeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name); | ||
| 450 | + const BBeforeMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name); | ||
| 451 | + const CBeforeMockShouldNotHaveDesc = Object.getOwnPropertyDescriptor(C, C.someTask.name); | ||
| 452 | + | ||
| 453 | + t.mock.method(C, C.someTask.name); | ||
| 454 | + C.someTask(msg); | ||
| 455 | + const BAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(B, B.someTask.name); | ||
| 456 | + | ||
| 457 | + const AAfterMockIsUnchanged = Object.getOwnPropertyDescriptor(A, A.someTask.name); | ||
| 458 | + const CAfterMockHasDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name); | ||
| 459 | + | ||
| 460 | + assert.strictEqual(CBeforeMockShouldNotHaveDesc, undefined); | ||
| 461 | + assert.ok(CAfterMockHasDescriptor); | ||
| 462 | + | ||
| 463 | + assert.deepStrictEqual(ABeforeMockIsUnchanged, AAfterMockIsUnchanged); | ||
| 464 | + assert.strictEqual(BBeforeMockIsUnchanged, BAfterMockIsUnchanged); | ||
| 465 | + assert.strictEqual(BBeforeMockIsUnchanged, undefined); | ||
| 466 | + | ||
| 467 | + assert.strictEqual(C.someTask.mock.restore(), undefined); | ||
| 468 | + const CAfterRestoreKeepsDescriptor = Object.getOwnPropertyDescriptor(C, C.someTask.name); | ||
| 469 | + assert.ok(CAfterRestoreKeepsDescriptor); | ||
| 470 | + }); | ||
| 471 | + | ||
| 322 | 472 | test('mocked functions report thrown errors', (t) => { | |
| 323 | 473 | const testError = new Error('test error'); | |
| 324 | 474 | const fn = t.mock.fn(() => { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments