Coder Social home page Coder Social logo

chuanqisun / jasmine-mock-factory Goto Github PK

View Code? Open in Web Editor NEW
8.0 3.0 5.0 164 KB

A Jasmine helper for creating mocked classes

License: MIT License

TypeScript 98.97% JavaScript 1.03%
mock jasmine moq testing jasmine-tests unit-test test-utils typescript

jasmine-mock-factory's Introduction

Jasmine Mock Factory

Build Status Coverage Status

A Jasmine test util that uses a TypeScript class or an instance of a class to create a mock instance of that class.

Quick Start

import { SomeClass } from 'some-library';
import { MockFactory} from 'jasmine-mock-factory';

it('should pass', () => {
    const mockInstance = MockFactory.create(SomeClass);

    /* arrange */
    mockInstance._spy.doSomething._func.and.returnValue('awesome!');

    /* act */
    mockInstance.doSomething();  // returns 'awesome!'

    /* assert */
    expect(mockInstance.doSomething).toHaveBeenCalled();
}

Quick reference

  /* 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

  • This util is built with and for Jasmine test framework. Basic understanding of Jasmine is assumed.
  • This util requires ES6 Proxy.

Usage

Install

npm install jasmine-mock-factory --save-dev

Import

Import the library with ES6 Module Syntax:

import { MockFactory } from 'jasmine-mock-factory'

Creating a mock

From a TypeScript class

class RealClass {
  // This is a typescript class
}

...

const mockInstance = MockFactory.create(RealClass);

From an instance of a class

const realInstance: RealInterface = new RealClass();

...

const mockInstance = MockFactory.create(realInstance);

From window objects

/* 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 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.
  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

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.
  mockInstance.publicFunction(42); // the spy behind it is invoked with 42

  (mockInstance as any).privateFunction(42);
  mockInstance['privateFunction'](42); // equivalent

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.
  /* 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

  /* 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

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.
  /* persist modification */
  mockInstance.publicProperty = 42;
  let temp = mockInstance.publicProperty; // temp = 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.
  /* 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

  • npm install to install dev dependencies
  • npm run build to build the library
  • npm run test to test the library

jasmine-mock-factory's People

Contributors

chuanqisun avatar dawidzq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

jasmine-mock-factory's Issues

Modernize test dependencies

The library currently depends on Angular Cli 1.1.0 to run all the jasmine tests. This is a unnecessary dependency that bloats the library by a lot. I'm adding this issue to collect ideas to track the work for modernizing the test dependencies. Ideally, typescript + jasmine + some light-weight test runner is all we need.

Key assignment error TypeScript 2.9

TypeScript 2.9 made changes to keys so that they can be of types string | number | symbol, instead of just string. In src/lib/index.ts lines 82, 85, and 92, propertyName: keyof T is being passed into ensureProperty which takes a string, causing an error to occur when compiling with TypeScript 2.9.

Adapt tool for jest

Unfortunately, this helper does not work for jest.
But there are few place to fix.

        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);
// =>
        const getterSpy = jest
            .spyOn(this.stub, propertyName, 'get')
            .mockImplementation(() => this.spy[propertyName]._value);
        const setterSpy = jest
            .spyOn(this.stub, propertyName, 'set')
            .mockImplementation((value) => (this.spy[propertyName]._value = value));
const spy = jasmine.createSpy(propertyName);
// =>
const spy = jest.fn().mockName(propertyName);

Maybe possible detect environment in runtime and call appropriate methods? Or it is better to create separate module for jest...

Definition of interface SpiedMember

export interface SpiedMember {
    _func?: jasmine.Spy;
    _get?: jasmine.Spy;
    _set?: jasmine.Spy;
}

Can these properties be defined as non-nullable?

When strictNullChecks is set to true, following recommended usage produce error:

service._spy.toDataObject._func.and.returnValue({ id: null }); // Object is possible undefined

to avoid this error I must use following constructions, which looks dirty.

service._spy.toDataObject._func && service._spy.toDataObject._func.and.returnValue

Use intersection types to allow strong typing of jasmine Spys

Hey nice project!

I was working on something similar was looking for something open source and found this. I found that you can tighten up the typing by defining an intersection type return on the create method. eg

export class MockFactory {
    /**
     * create a mock object that has the identical interface with the class you passed in
     */
    public static create<T extends object>(blueprint: Type<T> | T): Proxy<T> & {[M in keyof T]: jasmine.Spy} {

It allows you to do

mockClass1Instance = MockFactory.create(Class1);
mockClass1Instance.publicMethod.and.returnValue(999);

Without having to type cast mockClass1Instance.publicMethod as jasmine.Spy

Hope it helps!

access a getter spy error

var AppService = /** @class */ (function () {
    function AppService() {
        this.appInitialized$ = new BehaviorSubject(false);
    }
    Object.defineProperty(AppService.prototype, "isAppInitialized", {
        get: function () {
            return this.appInitialized$.getValue();
        },
        enumerable: true,
        configurable: true
    });
const appService = MockFactory.create(AppService);
appService ._spy.isAppInitialized._get.and.returnValue(true);
TypeError: Cannot read property 'getValue' of undefined
           // if target is property
            if (typeof this.prototype[propertyName] !== 'function') { // <--- error here
                // we add getters and setters to all properties to make the read and write spy-able
                var descriptor = {
                    get: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ function () { },
                    set: /* istanbul ignore next: Can't reach. spyOnProperty() requires its presence to install spies */ function (value) { },
                    enumerable: true,
                    configurable: true,
                };

Why Object.getOwnPropertyDescriptors(this.prototype)[propName] is not used?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.