113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
/** An object with helpers for mocking/spying on an object's property. */
|
|
export interface IPropertySpyHelpers<T, P extends keyof T> {
|
|
/**
|
|
* A `jasmine.Spy` for `get` operations on the property (i.e. reading the current property value).
|
|
* (This is useful in case one needs to make assertions against property reads.)
|
|
*/
|
|
getSpy: jasmine.Spy;
|
|
|
|
/**
|
|
* A `jasmine.Spy` for `set` operations on the property (i.e. setting a new property value).
|
|
* (This is useful in case one needs to make assertions against property writes.)
|
|
*/
|
|
setSpy: jasmine.Spy;
|
|
|
|
/** Install the getter/setter spies for the property. */
|
|
installSpies(): void;
|
|
|
|
/**
|
|
* Uninstall the property spies and restore the original value (from before installing the
|
|
* spies), including the property descriptor.
|
|
*/
|
|
uninstallSpies(): void;
|
|
|
|
/** Update the current value of the mocked property. */
|
|
setMockValue(value: T[P]): void;
|
|
}
|
|
|
|
/**
|
|
* Set up mocking an object's property (using spies) and return a function for updating the mocked
|
|
* property's value during tests.
|
|
*
|
|
* This is, essentially, a wrapper around `spyProperty()` which additionally takes care of
|
|
* installing the spies before each test (via `beforeEach()`) and uninstalling them after each test
|
|
* (via `afterEach()`).
|
|
*
|
|
* Example usage:
|
|
*
|
|
* ```ts
|
|
* describe('something', () => {
|
|
* // Assuming `window.foo` is an object...
|
|
* const mockWindowFooBar = mockProperty(window.foo, 'bar');
|
|
*
|
|
* it('should do this', () => {
|
|
* mockWindowFooBar('baz');
|
|
* expect(window.foo.bar).toBe('baz');
|
|
*
|
|
* mockWindowFooBar('qux');
|
|
* expect(window.foo.bar).toBe('qux');
|
|
* });
|
|
* });
|
|
* ```
|
|
*
|
|
* @param ctx The object whose property needs to be spied on.
|
|
* @param prop The name of the property to spy on.
|
|
*
|
|
* @return A function for updating the current value of the mocked property.
|
|
*/
|
|
export const mockProperty =
|
|
<T, P extends keyof T>(ctx: T, prop: P): IPropertySpyHelpers<T, P>['setMockValue'] => {
|
|
const {setMockValue, installSpies, uninstallSpies} = spyProperty(ctx, prop);
|
|
|
|
beforeEach(installSpies);
|
|
afterEach(uninstallSpies);
|
|
|
|
return setMockValue;
|
|
};
|
|
|
|
/**
|
|
* Return utility functions to help mock and spy on an object's property.
|
|
*
|
|
* It supports spying on properties that are either defined on the object instance itself or on its
|
|
* prototype. It also supports spying on non-writable properties (as long as they are configurable).
|
|
*
|
|
* NOTE: Unlike `jasmine`'s spying utilities, spies are not automatically installed/uninstalled, so
|
|
* the caller is responsible for manually taking care of that (by calling
|
|
* `installSpies()`/`uninstallSpies()` as necessary).
|
|
*
|
|
* @param ctx The object whose property needs to be spied on.
|
|
* @param prop The name of the property to spy on.
|
|
*
|
|
* @return An object with helpers for mocking/spying on an object's property.
|
|
*/
|
|
export const spyProperty = <T, P extends keyof T>(ctx: T, prop: P): IPropertySpyHelpers<T, P> => {
|
|
const originalDescriptor = Object.getOwnPropertyDescriptor(ctx, prop);
|
|
|
|
let value = ctx[prop];
|
|
const setMockValue = (mockValue: typeof value) => value = mockValue;
|
|
const setSpy = jasmine.createSpy(`set ${prop}`).and.callFake(setMockValue);
|
|
const getSpy = jasmine.createSpy(`get ${prop}`).and.callFake(() => value);
|
|
|
|
const installSpies = () => {
|
|
value = ctx[prop];
|
|
Object.defineProperty(ctx, prop, {
|
|
configurable: true,
|
|
enumerable: originalDescriptor ? originalDescriptor.enumerable : true,
|
|
get: getSpy,
|
|
set: setSpy,
|
|
});
|
|
};
|
|
const uninstallSpies = () =>
|
|
originalDescriptor ? Object.defineProperty(ctx, prop, originalDescriptor) : delete ctx[prop];
|
|
|
|
return {installSpies, uninstallSpies, setMockValue, getSpy, setSpy};
|
|
};
|