New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

jasmine-mock-factory

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jasmine-mock-factory - npm Package Compare versions

Comparing version 1.0.4 to 2.0.0

CHANGELOG.md

2

package.json
{
"name": "jasmine-mock-factory",
"version": "1.0.4",
"version": "2.0.0",
"description": "A Jasmine helper for creating mocked classes",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -15,10 +15,31 @@ # Jasmine Mock Factory

const mockInstance = MockFactory.create(SomeClass);
mockInstance.doSomething.and.returnValue('awesome!');
/* arrange */
mockInstance._spy.doSomething._func.and.returnValue('awesome!');
/* act */
mockInstance.doSomething(); // returns 'awesome!'
/* assert */
expect(mockInstance.doSomething).toHaveBeenCalled();
}
```
## Quick reference
```TypeScript
/* create a mock from a class*/
const mockInstance1 = MockFactory.create(RealClass);
/* create a mock from an instance*/
const mockInstance2 = MockFactory.create(realInstance);
/* access a function spy */
const spy1 = mockInstance._spy.functionName._func
/* access a getter spy */
const spy2 = mockInstance._spy.propertyName._get
/* access a setter spy */
const spy3 = mockInstance._spy.propertyName._set
```
## Prerequisite

@@ -28,7 +49,7 @@

This util requires [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and only contains un-compiled `*.ts` files which must be compiled with a [TypeScript](https://www.typescriptlang.org/) compiler.
This util requires [ES6 Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and only contains `*.ts` files that must be compiled with a [TypeScript](https://www.typescriptlang.org/) compiler.
## Usage
## Usage
### Install

@@ -67,31 +88,99 @@ ```Shell

#### From window objects
```TypeScript
/* make sure you have included dom types from the TypeScript library */
const mockWindow = MockFactory.create(window);
const mockDocument = MockFactory.create(document);
const mockLocation = MockFactory.create(location);
```
### Using a mock
`MockFactory.create()` will return an object with the same interface as the original object. You can invoke methods and get/set properties on this object.
* `MockFactory.create()` will return an object with the same interface as the real object. You can invoke functions and access properties on this object.
* In addition, the mock object provides a `_spy` facade, where you can access and config spies on functions and properties.
```TypeScript
const mockInstance = MockFactory.create(location);
mockInstance.reload(); // use it as if it were the real window.location
let temp = mockInstance.search; // use it as if it were the real window.search
mockInstance.hash = 'myHash'; // use it as if it were the real window.hash
mockInstance._spy.reload._func; // returns the spy behind location.reload
mockInstance._spy.search._get; // returns the spy behind the getter for location.search
mockInstance._spy.hash._set; // returns the spy behind the setter for location.hash
```
* All the public and private methods will have a jasmine.Spy as the initial value. The Spy cannot be overwritten.
* All the public and private properties will have `undefined` as the initial value. The value can be overwritten with anything.
### Examples
#### Invoking functions
* All functions will have a `jasmine.Spy` as the initial value. The spy cannot be overwritten and returns `undefined` by default.
* To access protected and private functions, cast the mockInstance `as any` or use bracket notation.
```TypeScript
class RealClass {
public doSomething(...arg: any[]) { ... }
public someProperty = 'whatever';
}
mockInstance.publicFunction(42); // the spy behind it is invoked with 42
const mockInstance = MockFactory.create(RealClass);
(mockInstance as any).privateFunction(42);
mockInstance['privateFunction'](42); // equivalent
```
// get, set property
expect(mockInstance.someProperty).toBeUndefined();
mockInstance.someProperty = 'hello';
expect(mockInstance.someProperty).toBe('hello');
#### Spying/stubbing functions
* You can change return values of functions or assert their calls by accessing them directly or through the `_spy` facade.
* Access a function spy on `mockInstance._spy.functionName._func`.
```TypeScript
/* stubbing a public function */
mockInstance._spy.publicFunction._func.and.returnValue(42);
(mockInstance.publicFunction as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting
// use function spy
expect(mockInstance.doSomething).not.toHaveBeenCalled();
/* stubbing a private function */
mockInstance._spy.privateFunction._func.and.returnValue(42);
((mockInstance as any).privateFunction as jasmine.Spy).and.returnValue(42); // equivalent, but not recommented because it requires casting twice
```
(mockInstance.doSomething as jasmine.Spy).and.returnValue('awesome!');
#### Accessing properties
* All properties have `undefined` as the initial value. The value can be overwritten with anything.
* You have read and write access to any property, even if they were readonly in the real object.
* To read or write a protected or private property, cast the mockInstance `as any` or use bracket notation.
* To write a readonly property, cast the mockInstance `as any`. The bracket notation won't work.
* By default, modification to the properties will persist, even if a getter or setter exists in the real object.
```TypeScript
/* persist modification */
mockInstance.publicProperty = 42;
let temp = mockInstance.publicProperty; // temp = 42;
expect(mockInstance.doSomething(42)).toBe('awesome!');
expect(mockInstance.doSomething).toHaveBeenCalledWith(42);
/* access readonly property */
mockInstance.readonlyProperty = 42; // typescript compiler error
(mockInstance as any).readonlyProperty = 42; // no problem
mockInstance['readonlyProperty'] = 42; // typescript compiler error
/* access private property */
(mockInstance as any).privateProperty = 'foo';
mockInstance['privateProperty'] = 'foo'; // equivalent
```
#### Spying/stubbing getters and setters
* All properties have spies on the getter and setter, even if the getter and setter don't exist in the real object.
* Access a getter spy on `mockInstance._spy.propertyName._get`.
* Access a setter spy on `mockInstance._spy.propertyName._set`.
* NOTE: modification to the properties will not persist after getter or setter spies are customized
* NOTE: `expect(mockInstance.someProperty).toBe(...)` will trigger `mockInstance._spy.someProperty._get`. Design the sequence of your assertions carefully to avoid shooting yourself in the foot.
```TypeScript
/* assert getter calls */
let temp = mockInstance.publicProperty;
expect(mockInstance._spy.publicProperty._get).toHaveBeenCalled();
/* assert setter calls on a public property */
mockInstance.publicProperty = 42;
expect(mockInstance._spy.publicProperty._set).toHaveBeenCalledWith(42);
/* customize setter */
expect(mockInstance.publicProperty).toBe(42); // pass. setter hasn't been customized
mockInstance._spy.publicProperty._set.and.callFake(() => { /* noop */});
mockInstance.publicProperty = 100;
expect(mockInstance.publicProperty).toBe(100); // fail. expect 42 to be 100. setter was customized
/* assert setter calls on a private property */
mockInstance['privateProperty'] = 42;
expect(mockInstance._spy.privateProperty._set).toHaveBeenCalledWith(42);
/* customize getter */
expect(mockInstance['privateProperty']).toBe(42); // pass. getter hasn't been customized
mockInstance._spy.privateProperty._get.and.returnValue(100);
mockInstance['privateProperty'] = 42;
expect(mockInstance['privateProperty']).toBe(42); // fail, expect 100 to be 42. getter was customzied
```
## Develope

@@ -98,0 +187,0 @@ This project is built with [Angular CLI](https://cli.angular.io/)

@@ -1,38 +0,55 @@

interface Type<T> extends Function {
new (...args: any[]): T;
export declare type Mock<T> = T & SpyFacade<T>;
export interface SpyFacade<T> {
_spy: Spied<T> & SpiedAny;
}
interface SpyMap {
[key: string]: jasmine.Spy;
export declare type Spied<T> = {
[K in keyof T]: SpiedMember;
}
interface ValueMap {
[key: string]: any;
export interface SpiedAny {
[id: string]: SpiedMember
}
class DynamicMockBase<T extends object> {
private spyMap: SpyMap = Object.create(null);
private valueMap: ValueMap = Object.create(null);
public handler = {
export interface SpiedMember {
_func?: jasmine.Spy;
_get?: jasmine.Spy;
_set?: jasmine.Spy;
}
interface Type<T> extends Function {
new (...args: any[]): T;
}
class DynamicBase<T extends object> {
public stubProxy: Mock<T>;
private stub = Object.create(null);
private spyProxy: T;
private spy = Object.create(null);
// create a spy before it is directly read/written
private stubProxyHandler = {
get: (target: T, propertyName: keyof T, receiver) => {
// trying to get a property, return value from valueMap.
if (typeof this.prototype[propertyName] !== 'function') {
return this.valueMap[propertyName];
}
if (propertyName === '_spy') {
return this.spyProxy;
}
// trying to get a function, if we haven't created the spy, create one
if (!this.spyMap[propertyName]) {
const spy = jasmine.createSpy(propertyName);
this.spyMap[propertyName] = spy;
}
this.ensureSpy(propertyName);
return this.spyMap[propertyName];
return this.stub[propertyName];
},
// store whatever user wants in the value map
set: (target, propertyName: keyof T, value, receiver) => {
if (propertyName === '_spy') {
throw Error('Cannot modify _spy. It is part of the MockFactory');
}
if (typeof this.prototype[propertyName] === 'function') {
throw Error(`Assignment not allowed because ${propertyName} is already a spied function`);
throw Error(`Cannot change ${propertyName} function, because MockFactory has already attached a permanent spy to it`)
}
this.valueMap[propertyName] = value;
this.ensureSpy(propertyName);
this.stub[propertyName] = value;
return true;

@@ -42,3 +59,71 @@ },

constructor(private prototype: T) {}
// create a spy before it is read from the spyFacade
private spyProxyHanlder = {
get: (target: T, propertyName: keyof T, receiver) => {
this.ensureSpy(propertyName);
return this.spy[propertyName];
},
set: (target, propertyName: keyof T, value, receiver) => {
throw Error(`Cannot change _spy.${propertyName}, because it is part of the MockFactory`);
},
}
constructor(private prototype: T) {
this.stubProxy = new Proxy<Mock<T>>(Object.create(null) as any as Mock<T>, this.stubProxyHandler);
this.spyProxy = new Proxy(Object.create(null), this.spyProxyHanlder);
}
private ensureSpy(propertyName: keyof T): void {
// create spy if needed
if (!this.spy[propertyName]) {
// if target is property
if (typeof this.prototype[propertyName] !== 'function') {
// we add getters and setters to all properties to make the read and write spy-able
const descriptor = {
get: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ () => {},
set: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ (value) => {},
enumerable: true,
configurable: true, // required by spyOnProperty
};
Object.defineProperty(this.stub, propertyName, descriptor);
// by default, let getter spy return whatever setter spy receives
const getterSpy = spyOnProperty(this.stub, propertyName, 'get').and.callFake(() => this.spy[propertyName]._value);
const setterSpy = spyOnProperty(this.stub, propertyName, 'set').and.callFake(value => this.spy[propertyName]._value = value);
this.spy[propertyName] = {
_value: undefined, // this is not on the public API, because _value will become meaningless once user customizes the spies.
_get: getterSpy,
_set: setterSpy,
}
Object.defineProperty(this.spy[propertyName], '_func', {
get: () => { throw Error(`can't get ${propertyName}._func because ${propertyName} is a property. You can config getter/setter spies via ${propertyName}._get and ${propertyName}._set`); },
set: () => { throw Error(`can't set ${propertyName}._func because ${propertyName} is a property. You can config getter/setter spies via ${propertyName}._get and ${propertyName}._set`); }
});
// if target is function
} else {
const spy = jasmine.createSpy(propertyName);
this.stub[propertyName] = spy;
this.spy[propertyName] = {
_func: spy,
_get: undefined,
_set: undefined,
};
Object.defineProperty(this.spy[propertyName], '_get', {
get: () => { throw Error(`can't get ${propertyName}._get because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); },
set: () => { throw Error(`can't set ${propertyName}._get because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); }
});
Object.defineProperty(this.spy[propertyName], '_set', {
get: () => { throw Error(`can't get ${propertyName}._set because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); },
set: () => { throw Error(`can't set ${propertyName}._set because ${propertyName} is a function. You can config function spy via ${propertyName}._func`); }
});
}
}
}
}

@@ -48,7 +133,8 @@

/**
* create a mock object that has the identical interface with the class you passed in
* create a mock object that has the identical interface as the class you passed in
*/
public static create<T extends object>(blueprint: Type<T> | T) {
public static create<T extends object>(blueprint: Type<T> | T): Mock<T> {
let prototype: T;
if (blueprint['prototype']) {
// get the prototype for a TypeScript class
prototype = blueprint['prototype'];

@@ -59,5 +145,5 @@ } else {

const mockBase = new DynamicMockBase(prototype);
return new Proxy<T>(mockBase as any as T, mockBase.handler);
const dynamicBase = new DynamicBase(prototype);
return dynamicBase.stubProxy;
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc