All files / testing / unstable_stub_property.ts

100.00% Branches 10/10
100.00% Lines 32/32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x7
x7
x7
x7
 
x97
 
x97
x436
x109
x109
x121
x121
x109
x109
 
x97
x183
x183
x183
x183
 
x183
 
x257
x257
x778
x97
 
x97
x97
x173
x173
x97
x97
 
x83
x83
x83


































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/**
 * Stubs a property on an object, retaining the attributes of the original property descriptor as far as possible.
 *
 * @experimental **UNSTABLE**: New API, yet to be vetted.
 *
 * @typeParam Self The type of the object to stub a property of.
 * @typeParam Prop The property of the instance to stub.
 * @param self The object to stub the property on.
 * @param property The property to stub.
 * @param value The value to stub the property with.
 * @returns A disposable that restores the original property when disposed.
 *
 * @example Usage
 * ```ts
 * import { stubProperty } from "@std/testing/unstable-stub-property";
 * import { assertEquals } from "@std/assert";
 *
 * const obj = { foo: "bar" };
 * {
 *  using stub = stubProperty(obj, "foo", "baz");
 *  assertEquals(obj.foo, "baz");
 * }
 * // After disposing, the property returns to its original value.
 * assertEquals(obj.foo, "bar");
 * ```
 */
export function stubProperty<Self, Prop extends keyof Self>(
  self: Self,
  property: Prop,
  value: Self[Prop],
): Disposable {
  const descriptor = Object.getOwnPropertyDescriptor(self, property);

  if (descriptor == null) {
    Object.defineProperty(self, property, { value, configurable: true });
    return {
      [Symbol.dispose]() {
        delete self[property];
      },
    };
  }

  if (!descriptor.configurable && !descriptor.writable) {
    throw new TypeError(
      `Cannot stub property "${
        String(property)
      }" because it is not configurable or writable.`,
    );
  }

  Object.defineProperty(self, property, {
    ...descriptor,
    ...(isAccessorDescriptor(descriptor) ? { get: () => value } : { value }),
  });

  return {
    [Symbol.dispose]() {
      Object.defineProperty(self, property, descriptor);
    },
  };
}

function isAccessorDescriptor(descriptor: PropertyDescriptor) {
  return Object.hasOwn(descriptor, "get") || Object.hasOwn(descriptor, "set");
}