lil-mocky 1.5.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # lil-mocky
2
2
 
3
- A lightweight JavaScript mocking library for testing. Create mock functions, objects, classes, and properties with call tracking and return value control. Includes spy functionality for tracking calls to existing methods.
3
+ A lightweight JavaScript mocking library for testing. Create mock functions, objects, and classes with call tracking and return value control. Includes spy functionality for tracking calls to existing methods.
4
4
 
5
5
  ## 🎯 Features
6
6
 
@@ -32,10 +32,10 @@ describe('User Service', () => {
32
32
  callback({ name: 'Alice', age: 30 });
33
33
 
34
34
  // Verify it was called correctly
35
- expect(callback.calls(0)).to.deep.equal({
35
+ expect(callback.calls[0]).to.deep.equal({
36
36
  user: { name: 'Alice', age: 30 }
37
37
  });
38
- expect(callback.calls().length).to.equal(1);
38
+ expect(callback.calls.length).to.equal(1);
39
39
  });
40
40
  });
41
41
  ```
@@ -69,10 +69,10 @@ const onComplete = mocky.fn().args('result').build();
69
69
  processUsers([{ name: 'Alice' }], onComplete);
70
70
 
71
71
  // Verify the callback was called correctly
72
- expect(onComplete.calls(0)).to.deep.equal({
72
+ expect(onComplete.calls[0]).to.deep.equal({
73
73
  result: [{ name: 'Alice', processed: true }]
74
74
  });
75
- expect(onComplete.calls().length).to.equal(1);
75
+ expect(onComplete.calls.length).to.equal(1);
76
76
  ```
77
77
 
78
78
  **Common patterns:**
@@ -97,7 +97,7 @@ expect(result.data).to.deep.equal([1, 2, 3]);
97
97
  // Arguments with defaults
98
98
  const logger = mocky.fn().args('message', { level: 'info' }).build();
99
99
  logger('Test message');
100
- expect(logger.calls(0)).to.deep.equal({
100
+ expect(logger.calls[0]).to.deep.equal({
101
101
  message: 'Test message',
102
102
  level: 'info'
103
103
  });
@@ -114,16 +114,18 @@ const mock = mocky.fn().build();
114
114
  mock.ret('hello');
115
115
  mock(); // Returns 'hello'
116
116
 
117
- // Any value type
117
+ // Any value type — including falsy values
118
118
  mock.ret(null);
119
- mock.ret(42);
119
+ mock.ret(0);
120
+ mock.ret(false);
121
+ mock.ret('');
120
122
  mock.ret([1, 2, 3]);
121
123
  mock.ret({ data: 'value' });
122
124
 
123
- // Different return per call (call numbers are 1-indexed)
124
- mock.ret('first', 1); // First call
125
- mock.ret('second', 2); // Second call
126
- mock.ret('default'); // All other calls (call 0 is the default)
125
+ // Different return per call (0-indexed)
126
+ mock.ret('first', 0); // First call
127
+ mock.ret('second', 1); // Second call
128
+ mock.ret('default'); // All other calls (no index = default)
127
129
 
128
130
  mock(); // 'first'
129
131
  mock(); // 'second'
@@ -138,7 +140,7 @@ const mock = mocky.fn().ret('pre-configured').build();
138
140
  mock(); // Returns 'pre-configured'
139
141
 
140
142
  // Per-call values work too
141
- const mock = mocky.fn().ret('default').ret('first', 1).build();
143
+ const mock = mocky.fn().ret('default').ret('first', 0).build();
142
144
 
143
145
  // Builder values are restored on reset
144
146
  mock.ret('override');
@@ -171,19 +173,17 @@ mock(); // Throws 'Something went wrong'
171
173
  // Non-Error values work too
172
174
  mock.throw('string error');
173
175
 
174
- // Per-call throwing
175
- mock.throw(new Error('first call only'), 1);
176
+ // Per-call throwing (0-indexed)
177
+ mock.throw(new Error('first call only'), 0);
176
178
  mock.ret('default');
177
179
 
178
180
  mock(); // Throws 'first call only'
179
181
  mock(); // Returns 'default'
180
182
  ```
181
183
 
182
- Note: `mock.ret(new Error(...))` also throws (legacy behavior). Use `.throw()` for clarity.
184
+ #### .calls - Verify Arguments
183
185
 
184
- #### .calls() - Verify Arguments
185
-
186
- Check what arguments were passed to the mock:
186
+ Check what arguments were passed to the mock. `calls` is a getter that returns the array of all calls:
187
187
 
188
188
  ```javascript
189
189
  const mock = mocky.fn().args('name', 'age').build();
@@ -192,17 +192,35 @@ mock('Alice', 30);
192
192
  mock('Bob', 25);
193
193
 
194
194
  // Get specific call
195
- mock.calls(0); // { name: 'Alice', age: 30 }
196
- mock.calls(1); // { name: 'Bob', age: 25 }
195
+ mock.calls[0]; // { name: 'Alice', age: 30 }
196
+ mock.calls[1]; // { name: 'Bob', age: 25 }
197
197
 
198
198
  // Get all calls
199
- mock.calls(); // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
200
- mock.calls().length; // 2
199
+ mock.calls; // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
200
+ mock.calls.length; // 2
201
201
 
202
202
  // Without .args() config, returns raw arguments array
203
203
  const rawMock = mocky.fn().build();
204
204
  rawMock('a', 'b', 'c');
205
- rawMock.calls(0); // ['a', 'b', 'c']
205
+ rawMock.calls[0]; // ['a', 'b', 'c']
206
+ ```
207
+
208
+ #### .data - Custom State
209
+
210
+ `data` is a plain object on the mock for storing custom state. It persists across calls and is cleared on reset:
211
+
212
+ ```javascript
213
+ const mock = mocky.fn((ctx) => {
214
+ ctx.data.count = (ctx.data.count || 0) + 1;
215
+ return ctx.data.count;
216
+ }).build();
217
+
218
+ mock(); // 1
219
+ mock(); // 2
220
+ mock.data.count; // 2
221
+
222
+ mock.reset();
223
+ mock.data; // {} (cleared)
206
224
  ```
207
225
 
208
226
  #### .reset() - Clear State
@@ -214,12 +232,12 @@ const mock = mocky.fn().build();
214
232
  mock.ret('value');
215
233
  mock('test');
216
234
 
217
- mock.calls().length; // 1
235
+ mock.calls.length; // 1
218
236
 
219
237
  mock.reset();
220
238
 
221
239
  // Everything cleared
222
- mock.calls().length; // 0
240
+ mock.calls.length; // 0
223
241
  mock(); // Returns undefined (ret cleared)
224
242
  ```
225
243
 
@@ -244,10 +262,9 @@ const mock = mocky.fn((ctx) => {
244
262
  ctx.args // Named arguments (from .args() config)
245
263
  ctx.rawArgs // Raw arguments array (before .args() processing)
246
264
  ctx.ret // Value set via .ret()
247
- ctx.call // Call number (1-indexed)
265
+ ctx.call // Call index (0-indexed)
248
266
  ctx.data // Custom state object (persists across calls, cleared on reset)
249
267
  ctx.original // Original function (available in spies)
250
- ctx.state // Internal state (prefer ctx.data instead)
251
268
 
252
269
  return someValue;
253
270
  }).args('param1', 'param2').build();
@@ -308,10 +325,10 @@ counter(); // 1
308
325
 
309
326
  ```javascript
310
327
  const fetcher = mocky.fn((ctx) => {
311
- if (ctx.call === 1)
328
+ if (ctx.call === 0)
312
329
  return { status: 'loading' };
313
330
 
314
- if (ctx.call === 2)
331
+ if (ctx.call === 1)
315
332
  return { status: 'success', data: [1, 2, 3] };
316
333
 
317
334
  return { status: 'cached' };
@@ -350,37 +367,13 @@ async function createUser(apiClient, userData) {
350
367
  const result = await createUser(api, { name: 'Alice' });
351
368
 
352
369
  // Verify the API was called correctly
353
- expect(api.post.calls(0)).to.deep.equal({
370
+ expect(api.post.calls[0]).to.deep.equal({
354
371
  url: '/users',
355
372
  data: { name: 'Alice' }
356
373
  });
357
374
  expect(result).to.deep.equal({ id: 123 });
358
375
  ```
359
376
 
360
- **Immutability after .build():**
361
-
362
- Once you call `.build()`, the mock object's structure is immutable. You cannot add or reassign properties:
363
-
364
- ```javascript
365
- // ❌ WRONG - can't modify after .build()
366
- const mock = mocky.obj({
367
- method: mocky.fn()
368
- }).build();
369
-
370
- mock.method = newImplementation; // TypeError: Cannot assign to read only property
371
- mock.newMethod = mocky.fn().build(); // TypeError: Cannot add property
372
-
373
- // ✅ RIGHT - define everything when building
374
- const mock = mocky.obj({
375
- method: mocky.fn((ctx) => {
376
- // Your custom implementation here
377
- return 'result';
378
- })
379
- }).build();
380
- ```
381
-
382
- This immutability prevents bugs and ensures `.reset()` works correctly. To change behavior during tests, use `.ret()` or `.reset()` instead of reassigning properties.
383
-
384
377
  **Nested mocks for complex structures:**
385
378
 
386
379
  ```javascript
@@ -429,7 +422,7 @@ api.newProp = 'added';
429
422
  api.reset();
430
423
 
431
424
  // After reset:
432
- // - api.get.calls() is []
425
+ // - api.get.calls is []
433
426
  // - api.get return values cleared
434
427
  // - api.baseURL is 'https://api.example.com' (restored)
435
428
  // - api.timeout is 5000 (restored)
@@ -483,26 +476,26 @@ await userService.createUser({ name: 'Alice' });
483
476
  authService.login('alice');
484
477
 
485
478
  // Verify each instance was used correctly
486
- expect(Logger.inst(0).constructor.calls(0)).to.deep.equal({
479
+ expect(Logger.inst(0).constructor.calls[0]).to.deep.equal({
487
480
  moduleName: 'UserService'
488
481
  });
489
- expect(Logger.inst(0).info.calls(0)).to.deep.equal({
482
+ expect(Logger.inst(0).info.calls[0]).to.deep.equal({
490
483
  message: 'Creating user'
491
484
  });
492
485
 
493
- expect(Logger.inst(1).constructor.calls(0)).to.deep.equal({
486
+ expect(Logger.inst(1).constructor.calls[0]).to.deep.equal({
494
487
  moduleName: 'AuthService'
495
488
  });
496
- expect(Logger.inst(1).info.calls(0)).to.deep.equal({
489
+ expect(Logger.inst(1).info.calls[0]).to.deep.equal({
497
490
  message: 'User logging in'
498
491
  });
499
492
 
500
- expect(Logger.numInsts()).to.equal(2);
493
+ expect(Logger.instCount).to.equal(2);
501
494
  ```
502
495
 
503
496
  **Accessing mock helpers on instances:**
504
497
 
505
- You can access `.calls()`, `.ret()`, and `.reset()` directly on instance methods:
498
+ You can access `.calls`, `.ret()`, and `.reset()` directly on instance methods:
506
499
 
507
500
  ```javascript
508
501
  const Logger = mocky.cls({
@@ -513,7 +506,7 @@ const logger = new Logger();
513
506
  logger.info('test message');
514
507
 
515
508
  // Access calls directly on the instance
516
- expect(logger.info.calls(0)).to.deep.equal({ message: 'test message' });
509
+ expect(logger.info.calls[0]).to.deep.equal({ message: 'test message' });
517
510
 
518
511
  // Configure returns on the instance
519
512
  logger.info.ret('logged');
@@ -521,7 +514,7 @@ expect(logger.info('another')).to.equal('logged');
521
514
 
522
515
  // Reset via instance
523
516
  logger.info.reset();
524
- expect(logger.info.calls().length).to.equal(0);
517
+ expect(logger.info.calls.length).to.equal(0);
525
518
  ```
526
519
 
527
520
  **Pre-configuring instances:**
@@ -549,6 +542,29 @@ const users = await db1.query('SELECT * FROM users'); // [{ id: 1, ... }]
549
542
  const empty = await db2.query('SELECT * FROM users'); // []
550
543
  ```
551
544
 
545
+ #### Instance Access
546
+
547
+ Use `Mock.inst(n)` to access or pre-configure a specific instance (lazy-creates the description if needed):
548
+
549
+ ```javascript
550
+ Mock.inst(0); // First instance
551
+ Mock.inst(1); // Second instance
552
+ ```
553
+
554
+ Use `instCount` (or `instanceCount`) to get the number of instantiated instances:
555
+
556
+ ```javascript
557
+ const Mock = mocky.cls({
558
+ method: mocky.fn()
559
+ }).build();
560
+
561
+ const inst1 = new Mock();
562
+ const inst2 = new Mock();
563
+
564
+ Mock.instCount; // 2
565
+ Mock.instanceCount; // 2 (synonym)
566
+ ```
567
+
552
568
  #### Using ctx.self for Instance State
553
569
 
554
570
  `ctx.self` is the "mockable surface" — the object that holds the mock's state. For class mocks, this is the description object (what `Mock.inst(n)` returns), not the raw class instance. Properties you want to access via `ctx.self` should be defined as members:
@@ -622,13 +638,13 @@ Mock.inst(0).method.ret('value');
622
638
  const instance1 = new Mock();
623
639
  const instance2 = new Mock();
624
640
 
625
- Mock.numInsts(); // 2
641
+ Mock.instCount; // 2
626
642
 
627
643
  Mock.reset();
628
644
 
629
645
  // After reset:
630
646
  // - All instance configurations cleared
631
- // - Mock.numInsts() is 0
647
+ // - Mock.instCount is 0
632
648
  // - Next instantiation starts fresh at instance 0
633
649
  ```
634
650
 
@@ -659,7 +675,7 @@ function notifyUser(user, message) {
659
675
  const result = notifyUser({ email: 'alice@example.com' }, 'Hello!');
660
676
 
661
677
  // Verify the method was called correctly
662
- expect(spy.calls(0)).to.deep.equal([
678
+ expect(spy.calls[0]).to.deep.equal([
663
679
  'alice@example.com',
664
680
  'Notification',
665
681
  'Hello!'
@@ -699,7 +715,7 @@ const spy = mocky.spy(obj, 'add', (ctx) => {
699
715
  }, ['x', 'y']);
700
716
 
701
717
  obj.add(3, 4); // 70 — (3 + 4) * 10
702
- spy.calls(0); // { x: 3, y: 4 }
718
+ spy.calls[0]; // { x: 3, y: 4 }
703
719
 
704
720
  spy.restore();
705
721
  ```
@@ -731,7 +747,7 @@ client1.request('/api/users', { method: 'GET' });
731
747
  client2.request('/api/posts', { method: 'GET' });
732
748
 
733
749
  // Both instances' calls are tracked
734
- expect(spy.calls().length).to.equal(2);
750
+ expect(spy.calls.length).to.equal(2);
735
751
  spy.restore();
736
752
  ```
737
753
 
@@ -745,7 +761,7 @@ Migration guide for Jest users:
745
761
  |------|-----------|
746
762
  | `jest.fn()` | `mocky.fn().build()` |
747
763
  | `mock.mockReturnValue(val)` | `mock.ret(val)` |
748
- | `mock.mock.calls[0][0]` | `mock.calls(0)` |
764
+ | `mock.mock.calls[0][0]` | `mock.calls[0]` |
749
765
  | `jest.spyOn(obj, 'method')` | `mocky.spy(obj, 'method')` |
750
766
  | `spy.mockRestore()` | `spy.restore()` |
751
767