
Stubs & Stunts — Lazy stubbing a TypeSctipt class or interface using Sinon.
Stubs & Stunts — Lazy stubbing a TypeSctipt class using Sinon.
Spies and stubs are known as test doubles.
Similar to how stunt doubles do the dangerous work in movies, we use test doubles to replace troublemakers and make tests easier to write.
When to Use Spies
As the name might suggest, spies are used to get information about function calls. For example, a spy can tell us how many times a function was called, what arguments each call had, what values were returned, what errors were thrown, etc.
As such, a spy is a good choice whenever the goal of a test is to verify something happened, so we can check many different results by using a simple spy.
The most common scenarios with spies involve.
- Checking how many times a function was called.
- Checking what arguments were passed to a function.
When to Use Stubs
Stubs are like spies, except in that they replace the target function. They can also contain custom behavior, such as returning values or throwing exceptions. They can even automatically call any callback functions provided as parameters.
Stubs have a few common uses:
- You can use them to replace problematic pieces of code.
- You can use them to trigger code paths that wouldn’t otherwise trigger — such as error handling.
- You can use them to help test asynchronous code more easily.
Stubbing an Entire Class
At times you may want to stub all the methods of a class (a service for example). This can be achieved by creating stubbed instance for this class. You can override the implementation of some of the stubs.
Furthermore, you may want to stub a class WITHOUT calling its constructor for multiple reasons:
- Running the constructor may cause unwanted side effects (such as opening a connection or assigning resources)
- There is no default constructor, therefore calling the constructor may require other instance of Classes which are not relevant to your test.
Imagine we have a service class we need to mock in order to test a component using this service.
class MyClass {
constructor(input: number) {
throw new Error("Should not be called");
}
func(input: number, text: string) {
console.log(text);
return input;
}
property: number = 3;
optionalProperty?: number;
get getter(): number {
return this.property;
}
set setter(value: number) {
this.property = value;
}
propertyFunc = (int: number) => int;
asyncPropertyFunc = async (int: number) => await Promise.resolve(int);
async asynFunc(value: number) {
return await Promise.resolve(9);
}
}
Using Sinon.Js
We’ll be using Sinon.js, which is a standalone and test framework agnostic JavaScript test spies, stubs and mocks. Sinon has a method called createStubInstance which looks like it’s perfect for our needs. Let’s try it.
Let’s start with stubbing a class function:
it("should stub class function", () => {
const mockMyClass = sinon.createStubInstance(MyClass);
mockMyClass.func.returns(9);
expect(mockMyClass.func(5, "hello")).to.eq(9);
});

Great! Now Let’s try a property method:
it("should stub property class function", () => {
const mockMyClass = sinon.createStubInstance(MyClass);
mockMyClass.propertyFunc.returns(9);
expect(mockMyClass.propertyFunc(5)).to.eq(9);
});

Why is propertyFunc undefined?
Well, properties don’t exist until an object constructs them. If we look at the constructor of MyClass in runtime, this is what we’ll see:

So we need the constructor to be called for the properties to exist, but we want to avoid calling the constructor.
Let’s Create a TypeScript Stubbed Instance Builder
We will be using Java Script Proxy to create the stubbed instance.
The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object.
You create a Proxy with two parameters:
- target: the original object which you want to proxy
- handler: an object that defines which operations will be intercepted and how to redefine intercepted operations.
The Proxy’s get method will create a stub when a function is accessed. Even a property method.
export interface Stub extends SinonStub {}
export type StubbedInstance = sinon.SinonStubbedInstance & T;
export const createStubbedInstance = () => {
const buildStub = (
overrides: Partial = {}
): StubbedInstance => {
let overrideValues: Record = overrides;
const built: Record = ({ ...overrideValues }) as any;
const createStubIfNeeded = (
target: Record,
prop: string
) => {
if (!target[prop]) {
const stub = sinon.stub();
target[prop] = stub;
}
};
const builder = new Proxy(built, {
get(target, prop: string) {
createStubIfNeeded(target, prop);
return target[prop];
},
set(target, prop: string, args?: any) {
built[prop] = args;
return true;
}
});
return builder as StubbedInstance;
};
return buildStub;
};
Let’s try the same test, this time using the createStubbedInstance method from the new StubBuilder:
it("should stub class function", () => {
const mockMyClass = createStubbedInstance()();
mockMyClass.func.returns(9);
expect(mockMyClass.func(5)).to.eq(9);
});
it("should stub property class function", () => {
const mockMyClass = createStubbedInstance()();
mockMyClass.propertyFunc.returns(9);
expect(mockMyClass.func(5)).to.eq(9);
});
This time, both tests are passing

Setters and Getters
For this to work with setters and getters, we must set their override values like so:
it("should override class property", () => {
const mockMyClass = createStubbedInstance(){ property: 5 });
expect(mockMyClass.property).to.eq(5);
});
it("should override class getter", () => {
const mockMyClass = createStubbedInstance() { getter: 5 });
expect(mockMyClass.getter).to.eq(5);
});
Summary
Stubbing an entire instance is easy when using a Proxy.
If you wish to see the complete code and tests, take a look at this repo: StubBuilder implementation & StubBuilder tests
If you wish to use the StubBuilder you can use it be installing the cypress-test-utils-npm
Happy Testing!