How to mock an / any instance method of an object — RSpec to Jest

Ly Channa
3 min readJan 3, 2023

--

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)
})
})
})

--

--

Ly Channa

Highly skilled: REST API, OAuth2, OpenIDConnect, SSO, TDD, RubyOnRails, CI/CD, Infrastruct as Code, AWS.