How to mock an / any instance method of an object — RSpec to Jest
After working with TypeScript on several projects, I’ve realized it’s challenging to find documentation in Jest for mocking an instance method or any instance method of a class. If you’re familiar with RSpec, I hope this insight saves you a few hours of searching online
Mock an instance method
# rspec
subject = described_class.new
allow(subject).to receive(:method).and_return(any_value)
...
subject.method
// jest typescript
let subject = new describedClass()
jest.spyOn(subject, "method").mockImplementation(()=>{ any_value})
...
subject.method()
// you need to restore the implementation before the mock at the end
jest.restoreAllMocks();
Mock any instance method
# rspec
allow_any_instance_of(described_class).to receive(:call).and_return(any_value)
...
described_class.call
// jest typescript
jest.spyOn(describedClass.prototype, "call").mockImplementation(()=>{ any_value})
...
describedClass.call()
// you need to restore the implementation before the mock manually
jest.restoreAllMocks();
For async method
// async method
jest.spyOn(describedClass.prototype, 'callAsync').mockImplementation(async () => { jest.fn().mockResolvedValue('') });
...
await describedClass.callAsync()
// you need to restore the implementation before the mock manually
jest.restoreAllMocks();
Some real examples extracted from https://github.com/channainfo/DaiTol/blob/master/__tests__/src/executor.test.ts
import { ExecResult } from "./exec_result";
import { ExecFailureError } from "./exec_failure_error";
import { ExecParam } from "./exec_param";
import DaiTol from ".";
export class Executor {
public execResult: ExecResult
public execParam: ExecParam
constructor(options?: Map<string, any>) {
this.execParam = options ? ExecParam.from(options) : new ExecResult()
this.execResult = new ExecResult()
}
public static async callAsync(options?: Map<string, any>): Promise<ExecResult> {
let object = options ? new this(options) : new this()
try {
await object.callAsync()
return object.execResult
}
catch (ex) {
return object.handleError(ex);
}
}
public static call(options?: Map<string, any>): ExecResult {
let object = options ? new this(options) : new this()
try {
object.call()
return object.execResult
}
catch (ex) {
return object.handleError(ex);
}
}
public handleError(ex: any): ExecResult {
let error = ex as Error;
this.execResult.failByMessage(error.message)
return this.execResult
}
// override and set execResult
public async callAsync() {
throw new DaiTol.ExecFailureError('callAsync need to be implemented')
}
// override and set execResult
public call() {
throw new DaiTol.ExecFailureError('call need to be implemented')
}
}
Test
import { describe } from "@jest/globals";
import DaiTol from "../../src";
describe("Executor", () => {
let describedClass = DaiTol.Executor
describe("constructor", () => {
it("accept default options", () => {
let subject = new describedClass()
expect(subject.execResult).toBeInstanceOf(DaiTol.ExecResult)
})
it("accept options", () => {
let accountOptions: Map<string, any> = new Map<string, any>(
[
["name", "Joe ann"],
["age", 27]
]
)
let subject = new DaiTol.Executor(accountOptions)
expect(subject.getParam("name")).toEqual("Joe ann")
expect(subject.getParam("age")).toEqual(27)
})
})
describe('#handleError', () => {
let subject = new describedClass()
it("return set exec result failed and set error message", () => {
let errorMessage = "Error set from executor"
let error = new DaiTol.ExecFailureError(errorMessage)
let execResult = subject.handleError(error)
expect(execResult.isSuccess()).toEqual(false)
expect(execResult.errorMessage()).toEqual(errorMessage)
})
})
describe(".call", () => {
it("accept default options", () => {
let execResult = describedClass.call()
expect(execResult).toBeInstanceOf(DaiTol.ExecResult)
})
it("require call to be implemented", () => {
let execResult = describedClass.call()
expect(execResult.errorMessage()).toEqual('call need to be implemented')
expect(execResult.isSuccess()).toEqual(false)
})
it("execute the call and return exec result", () => {
jest.spyOn(describedClass.prototype, 'call').mockImplementation(() => { });
let options = new Map<string, any>()
let execResult = describedClass.call(options)
expect(execResult).toBeInstanceOf(DaiTol.ExecResult)
expect(execResult.isSuccess()).toEqual(true)
jest.restoreAllMocks();
})
})
describe('#call', () => {
it("throw an ExecFailureError", () => {
let subject = new describedClass()
expect(() => { subject.call() }).toThrowError(DaiTol.ExecFailureError)
})
})
describe(".callAsyn", () => {
it("accept default options", async () => {
let execResult = await describedClass.callAsync()
expect(execResult).toBeInstanceOf(DaiTol.ExecResult)
})
it("require callAsync to be implemented", async () => {
let execResult = await describedClass.callAsync()
expect(execResult.isSuccess()).toEqual(false)
expect(execResult.errorMessage()).toEqual('callAsync need to be implemented')
})
it("execute the call and return exec result", async () => {
jest.spyOn(describedClass.prototype, 'callAsync').mockImplementation(async () => { jest.fn().mockResolvedValue('') });
let options = new Map<string, any>()
let execResult = await describedClass.callAsync(options)
expect(execResult).toBeInstanceOf(DaiTol.ExecResult)
expect(execResult.isSuccess()).toEqual(true)
jest.restoreAllMocks();
})
})
describe('#callAsync', () => {
it("throw an ExecFailureError", async () => {
let subject = new describedClass()
expect(subject.callAsync()).rejects.toThrowError(DaiTol.ExecFailureError)
})
})
})