Skip to content

Instantly share code, notes, and snippets.

@Maximilianos
Created May 27, 2016 11:08
Show Gist options
  • Save Maximilianos/e836ee599e539015c4f2a8a0ea0cdcec to your computer and use it in GitHub Desktop.
Save Maximilianos/e836ee599e539015c4f2a8a0ea0cdcec to your computer and use it in GitHub Desktop.
Basic Spying and Stubbing for functions in JavaScript
import deepEqual from 'deep-equal';
/**
* A basic Map implementation whose keys hold a deep equality
* relationship but not a referential equality relationship
*
*/
export default class LooseMap {
constructor() {
this.__entries__ = [];
}
set(key, value) {
this.__entries__.unshift([key, value]);
}
get(key) {
const entry = this.__entries__.find(
entry => deepEqual(entry[0], key)
);
return entry && entry[1];
}
has(key) {
return !!~this.__entries__.findIndex(
entry => deepEqual(entry[0], key)
);
}
}
/**
* Exposes the number of times the
* `call` method is called
*
*/
export class Spy {
constructor() {
this.call = this.call.bind(this);
this.call.called = 0;
}
call() {
this.call.called += 1;
}
}
/**
* Replace a function with the `call` method
* of an initiallized Spy instance
*
*/
export default function spy() {
return (new Spy()).call;
}
import LooseMap from './LooseMap';
import { Spy } from './spy';
/**
* Exposes the number of times the `call` method is
* called and allows you to specify what the `call`
* method returns for a given input
*
*/
export class Stub extends Spy {
constructor() {
super();
this.returnValues = new LooseMap();
// set up the default return
this.defaultReturn = Symbol('default stub return key');
this.returnValues.set(this.defaultReturn, undefined);
// stub.returns(...) should override the default return
this.call.returns = this.returns.bind(this, this.defaultReturn);
this.call.withArgs = this.withArgs.bind(this);
}
call(...args) {
super.call();
// check if return value is defined for given
// args else return the default return
const key = this.returnValues.has(args)
? args : this.defaultReturn;
return this.returnValues.get(key);
}
returns(inputs, output) {
this.returnValues.set(inputs, output);
}
withArgs(...args) {
return { returns: this.returns.bind(this, args) };
}
}
/**
* Replace a function with the `call` method
* of an initiallized Stub instance
*
*/
export default function stub() {
return (new Stub()).call;
}
import stub from './stub';
// the function we are going to use for our example
let foo = () => 'Oh Yeah!!!!';
// Before stubbing the function
bin.log(foo());
// We stub the function!
foo = stub();
// check that function calls are being recorded
bin.log(foo.called); // should be 0 because function hasn't been
// called since being made a stub
bin.log(foo()); // should return undefined because a stub by itself
// does not do anything
bin.log(foo.called); // should be 1 because function has been
// called once
// check that we can set what the stub returns
foo.returns('I am a stub');
bin.log(foo()); // should be "I am a stub"
bin.log(foo('foo', 'bar')); // should be "I am a stub"
// check that we can set what the stub returns
// for a given set of inputs
foo.withArgs('foo', 'bar').returns('So original');
bin.log(foo('foo', 'bar')); // should be "So original"
@Maximilianos
Copy link
Author

This code is available as a bin on webpackbin: http://www.webpackbin.com/E1PxnZWX-

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment