Learn how to set expectations on the object returned from a spied method, Jasmine

I am currently faced with setting an expectation regarding the number of times a specific function called "upsertDocument" is executed. This function is part of a DocumentClient object within a getClient method in production. Here's how it looks:

client.ts file:

getClient() {
    return new DocumentClient(...);
}

In the method I aim to unit test:

import * as clientGetter from '../clients/client'
...
methodA(){
  ...
  client = clientGetter.getClient(); 
  for (...) {
    client.upsertDocument();
  }
}

The issue arises from the fact that the DocumentClient constructor establishes a live connection with the database, which obviously isn't suitable for unit testing. My options are either to spyOn the DocumentClient constructor or the getClient method, both of which present challenges.

If I spy on the DocumentClient constructor:

unit test:

it('performs specific actions', (done: DoneFn) => {
  spyOn(DocumentClient, 'prototype').and.returnValue({
    upsertDocument: () => {}
  })

  ...
})

This results in the following error:

  Message:
    Error: <spyOn> : prototype is not declared writable or has no setter
    Usage: spyOn(<object>, <methodName>)
  Stack:
    Error: <spyOn> : prototype is not declared writable or has no setter
    Usage: spyOn(<object>, <methodName>)
        at <Jasmine>
        at UserContext.fit (C:\Users\andalal\workspace\azure-iots-saas\service-cache-management\src\test\routes\managementRoute.spec.ts:99:35)
        at <Jasmine>
        at runCallback (timers.js:810:20)
        at tryOnImmediate (timers.js:768:5)
        at processImmediate [as _immediateCallback] (timers.js:745:5)

If I spy on the getClient method and provide my own object with an upsertDocument method, I face difficulty in setting an expectation on that mock object:

it('performs specific actions', (done: DoneFn) => {
  spyOn(clientGetter, 'getClient').and.returnValue({
    upsertDocument: () => {}
  })

  methodA().then(() => {
    expect().toHaveBeenCalledTimes(3); // what do I put in expect() ??
  })
})

Answer №1

After identifying that the issue had already been tackled through an existing unit test, I decided to provide a comprehensive solution for others facing similar challenges:

Essentially, the getClient method is responsible for returning a DocumentClient with specific methods. The goal here is to avoid invoking the actual DocumentClient constructor. To achieve this, I developed a mock object that mirrored the DocumentClient interface and utilized jasmine.createSpy on each method:

export class TestableDocumentClient implements IDocumentClient {
  upsertDocument = jasmine.createSpy('upsertDocument')
  ...
}

In my unit test, I intercepted the getClient method and provided an instance of the aforementioned mock type:

let mockDocClient = new TestableDocumentClient()
spyOn(clientGetter, getClient).and.returnValue(mockDocClient);

...

expect(mockDocClient.upsertDocument).toHaveBeenCalledTimes(3);

To ensure everything was functioning as expected, I created a basic object containing only the desired method and verified its behavior through expectations, which yielded successful outcomes.

let myObj = {
  upsertDocument: jasmine.createSpy('upsertDocument')
}

spyOn(clientGetter, 'getClient').and.returnValue(myObj);

...
expect(myObj.upsertDocument).toHaveBeenCalledTimes(3);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to consolidate two interface types while combining the overlapping properties into a union type?

Is there a way to create a type alias, Combine<A, B>, that can merge properties of any two interfaces, A and B, by combining their property types using union? Let's consider the following two interfaces as an example: interface Interface1 { t ...

What's the process for deducing the default generic parameter from a function type?

Is there a way to extract the default type parameter of a function in order to make this statement compile successfully? const fails: string = "" as ReturnType<<T = string>() => T>; ...

What could be causing TypeScript to raise an issue when a /// reference comes after the 'use strict' statement?

This particular inquiry is somewhat connected to a question I posted on Stack Overflow yesterday about why TypeScript is encountering issues when trying to import a module. The initial configuration remains unchanged. My TypeScript file appears as follows ...

Tips for adding and verifying arrays within forms using Angular2

Within my JavaScript model, this.profile, there exists a property named emails. This property is an array composed of objects with the properties {email, isDefault, status}. Following this, I proceed to define it as shown below: this.profileForm = this ...

What is the process for removing a document with the 'where' function in Angular Fire?

var doc = this.afs.collection('/documents', ref => ref.where('docID', '==', docID)); After successfully retrieving the document requested by the user with the code above, I am unsure of how to proceed with deleting that do ...

Unusual Type Inference in Typescript {} when Evaluating Null or Undefined

Upon upgrading from typescript 4.9.3 to 5.0.2, we encountered an error when asserting types. Can someone explain why the function "wontWorking" is not functioning correctly? I expected this function to infer v as Record<string, any>, but instead it ...

What could be causing the issue of CSS Styles not being applied to an Angular 2 component with Vaadin elements?

Currently, I am immersed in the tutorial on Vaadin elements using Angular 2 that I stumbled upon here In section 5.3, Styles are applied to the app.component.ts as shown below import { Component } from [email protected]/core'; @Component({ select ...

Issue encountered with NextJS where the post request utilizing Bcrypt is not being recognized

In the process of developing a basic login feature using nextJS, I have successfully managed to save new usernames and encrypted passwords from the registration page. The login functionality is intended to be similar, but requires comparing the password st ...

Angular checkbox filtering for tables

I have a table populated with data that I want to filter using checkboxes. Below is the HTML code for this component: <div><mat-checkbox [(ngModel)]="pending">Pending</mat-checkbox></div> <div><mat-checkbox [(ngModel ...

What is the process of declaring a global function in TypeScript?

I am looking to create a universal function that can be accessed globally without needing to import the module each time it is used. The purpose of this function is to serve as an alternative to the safe navigation operator (?) found in C#. I want to avoi ...

I am experiencing slow load times for my Angular 2 app when first-time users access it, and I am seeking assistance in optimizing its speed

Below, you'll find a snippet from my app.ts file. I'm currently working with angular2, firebase, and typescript. I'm curious if the sluggish performance is due to the abundance of routes and injected files? The application functions smoot ...

Guide on importing an ES6 package into an Express Typescript Project that is being utilized by a Vite React package

My goal is to efficiently share zod models and JS functions between the backend (Express & TS) and frontend (Vite React) using a shared library stored on a gcloud npm repository. Although the shared library works flawlessly on the frontend, I continue to e ...

Executing the Ionic code within the Xcode Swift environment

I have developed an Ionic application that I successfully compiled in Xcode for iOS. Additionally, I have integrated a widget into the application. My goal is to set it up so that when the application is opened using the regular app icon, it displays the m ...

What is the best way to handle closing popups that have opened during an error redirection

In my interceptor, I have a mechanism for redirecting the page in case of an error. The issue arises when there are popups already open, as they will not automatically close and the error page ends up appearing behind them. Here's the code snippet re ...

Having trouble compiling Typescript code when attempting to apply material-ui withStyles function

I have the following dependencies: "@material-ui/core": "3.5.1", "react": "16.4.0", "typescript": "2.6.1" Currently, I am attempting to recreate the material-ui demo for SimpleListMenu. However, I am encountering one final compile error that is proving ...

Creating a global variable in Angular that can be accessed by multiple components is a useful technique

Is there a way to create a global boolean variable that can be used across multiple components without using a service? I also need to ensure that any changes made to the variable in one component are reflected in all other components. How can this be ac ...

Can you explain the mechanics behind Angular Component CSS encapsulation?

Is it possible to avoid CSS conflicts when using multiple style sheets? Consider Style 1: .heading { color: green; } And Style 2: .heading { color: blue; } If these two styles are applied in different views and rendered on a layout as a Partial Vi ...

What is the best way to bypass TS1192 when incorporating @types/cleave.js into my Angular application?

Using cleave.js (^1.5.2) in my Angular 6 application, along with the @types/cleave.js package (^1.4.0), I encountered a strange issue. When I run ng build to compile the application, it fails and shows the following errors: ERROR in src/app/app.component. ...

Error encountered due to a circular reference in the dependency library

Whenever I attempt to run my application, I encounter the following error: > npm start Starting the development server... ts-loader: Using <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="42363b32273121302b323602716c776c71"& ...

Sending a POST request in Node.js and Express may result in the request body being empty or undefined

Here is a snippet of my Typescript code: import express = require('express'); const app: express.Application = express(); const port: number = 3000; app.listen(port, () => { console.log("The server is now running on port" + port); ...