Using Jasmine to spy on an imported module in TypeScript

My current challenge involves testing a utility class that contains static methods using jasmine and typescript. The issue arises from the fact that this helper class relies on a 3rd party library to accomplish a particular task. I must validate that this 3rd party library is invoked in all scenarios.

import Helpers from '../../src/utils/Helpers';
import {parseString} from 'xml2js';



describe('Helper class', function() {
  let mockParseString: any;

  describe('xmlToJson', function() {
    beforeEach(function(done) {
      mockParseString = jasmine.createSpy('parseString', parseString);
      // spyOn(xml2js, 'parseString').and.callFake(function(xml: string, callback: (error: any, data: object) => void) {
      //
      // });

      setTimeout(() => {
        done();
      }, 1);
    })


    it('calls library to parse string', async function(done) {
      await Helpers.xmlToJson('<div></div>');

      expect(mockParseString).toHaveBeenCalled();
      done();
    })
  })
});

Within the helper class, I am simply encapsulating a callback function within a promise:

import {convertableToString, OptionsV2, parseString} from 'xml2js';
export default class Helpers {
  public static xmlToJson(xml: convertableToString, options?: OptionsV2): Promise<any> {
    return new Promise((resolve, reject) => {
      if(options) {
        parseString(xml, (err, results) => {
          if(err) {
            reject(err);
          }

          resolve(results);
        });
      } else {
        parseString(xml, options, (err, results) => {
          if(err) {
            reject(err);
          }

          resolve(results);
        });
      }
    })
  }
}

Unfortunately, I have encountered an error indicating that the spy is not being triggered. Despite my efforts, I have been unable to find a solution to make the spy function as expected. It's possible that this limitation may be unavoidable.

EDIT

This is how I am executing the test:

 ./node_modules/.bin/ts-node ./node_modules/.bin/jasmine spec/utils/Helpers-spec.ts 

Answer №1

Below is a functional test to validate your code:

import Helpers from '../../src/utils/Helpers';
import * as xml2js from 'xml2js';

describe('Helper class', function() {

  let mockParseString;

  describe('xmlToJson', function() {

    beforeAll(() => {
      mockParseString = spyOn(xml2js, 'parseString').and.callThrough();
    });

    it('verifies if the library is invoked to parse a string', (done) => {
      (async () => {
        await Helpers.xmlToJson('<div></div>');
        expect(mockParseString).toHaveBeenCalled();
      })().then(() => done());
    });
  });
});

Answer №2

Unfortunately, my attempts to make this work by referencing the library implicitly were unsuccessful. Instead, I decided to implement a dependency injection solution and make my class a service. Ultimately, this approach is probably cleaner. (I still need to tidy up the types as they are currently a bit messy)

import {convertableToString, OptionsV2, parseString} from 'xml2js';

export default class Helpers {
  parseString: any;

  constructor(parseString: any) {
    this.parseString = parseString;
  }
  public xmlToJson(xml: convertableToString, options?: OptionsV2): Promise<any> {
    return new Promise((resolve, reject) => {
      if(options) {
        this.parseString(xml, (err: any, results: string) => {
          if(err) {
            reject(err);
          }

          resolve(results);
        });
      } else {
        this.parseString(xml, options, (err: any, results: string) => {
          if(err) {
            reject(err);
          }

          resolve(results);
        });
      }
    })
  }
}

Next, I performed a basic test with:

import Helpers from '../../src/utils/helpers';
import * as xml2js from 'xml2js';

describe('Helper class', function() {

  let mockParseString:any;

  describe('xmlToJson', function() {

    beforeAll(() => {
      mockParseString = spyOn(xml2js, 'parseString').and.callThrough();
    });

    it('calls library to parse string', async (done) => {
      let helper = new Helpers(mockParseString);

      await helper.xmlToJson('<div></div>');
      expect(mockParseString).toHaveBeenCalled();
      done();
    });
  });
});

Huge thanks to @brian-lives-outdoors for the assistance!

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

Angular's counterpart to IWebProxy

When using C#, I am able to: public static IWebProxy GetWebProxy() { var proxyUrl = Environment.GetEnvironmentVariable("HTTPS_PROXY"); if (!string.IsNullOrEmpty(proxyUrl)) { var proxy = new WebProxy { Address = new Ur ...

What is the importance of using a server to host an Angular 2 app?

I recently finished developing a front-end application in Angular 2 using angular-cli. After completion, I created a bundle using the command ng build. Here's what puzzles me - my app consists only of JavaScript and HTML files. So why do I need to h ...

Error Encountered with Next.js 13.4.1 when using styled-components Button in React Server-Side Rendering

I am currently working on a React project using Next.js version 13.4.1 and styled-components. One problem I'm facing is with a custom Button component that I've created: import React from 'react'; import styled from 'styled-compone ...

In order to retrieve specific object attributes using the unique identifier

I am currently managing 2 API's referred to as teachers and sessions. The contents of the teachers JSON file are: [ { "teacherName": "Binky Alderwick", "id": "01" }, { "teacherName": "Basilio Gregg", ...

The TypeScript compiler is searching in an external directory for the node_modules folder

My angular 2 project is located in the directory /c/users/batcave/first_project. In that same directory, I have various files such as index.html, systemjs.config.js etc., and a node_modules folder that only contains @types and typescript. This means my @a ...

How come Typescript claims that X could potentially be undefined within useMemo, even though it has already been defined and cannot be undefined at this stage

I am facing an issue with the following code snippet: const productsWithAddonPrice = useMemo(() => { const addonsPrice = addonsSelected .map(id => { if (addons === undefined) { return 0} return addons.find(addon => addo ...

Dynamic Object properties are not included in type inference for Object.fromEntries()

Hey there, I've been experimenting with dynamically generating styles using material UI's makeStyles(). However, I've run into an issue where the type isn't being correctly inferred when I use Object.fromEntries. import * as React from ...

Executing the Angular 2 foreach loop before the array is modified by another function

Currently, I am facing some difficulties with an array that requires alteration and re-use within a foreach loop. Below is a snippet of the code: this.selectedDepartementen.forEach(element => { this.DepID = element.ID; if (this.USERSDepIDs. ...

Can you explain the functionality of `property IN array` in the TypeORM query builder?

I'm looking to filter a list of entity ids using query builder in an efficient way. Here's the code snippet I have: await this._productRepo .createQueryBuilder('Product') .where('Product.id IN (:...ids)', { ids: [1, 2, 3, 4] ...

utilize the getStaticProps function within the specified component

I recently started a project using Next.js and TypeScript. I have a main component that is called in the index.js page, where I use the getStaticProps function. However, when I log the prop object returned by getStaticProps in my main component, it shows a ...

Using Typescript to collapse the Bootstrap navbar through programming

I've managed to make Bootstrap's navbar collapse successfully using the data-toggle and data-target attributes on each li element. If you're interested, here is a SO answer that explains a way to achieve this without modifying every single ...

Initiating the ngOnInit lifecycle hook in child components within Angular

I am facing an issue with controlling the behavior of child components in my Angular application. The problem arises when switching between different labels, causing the ngOnInit lifecycle hook of the children components not to trigger. The main component ...

The functionality of MaterializeCSS modals seems to be experiencing issues within an Angular2 (webpack) application

My goal is to display modals with a modal-trigger without it automatically popping up during application initialization. However, every time I start my application, the modal pops up instantly. Below is the code snippet from my component .ts file: import ...

Utilizing Generic Types for Object Parameters

Why am I encountering an error when trying to use a function of type A for a B type one? How can I define a function type with unknown properties? When I attempt to define props in type B as props: Record<string, unknown>, it results in a similar err ...

What is the best way to determine which variable holds the greatest value in Angular?

I am working with different array lengths stored in variables and trying to determine which variable is the greatest, then change the font color of that variable. However, I encountered an issue where if two variables have the same value, only one is being ...

Experiencing an Issue with NGINX Loading Vue 3 Vite SPA as a Blank White Page

I currently have the following NGINX configuration set up: events { worker_connections 1024; } http { server { listen 80; server_name localhost; location / { root C:/test; index index.html; ...

Combine iron-page and bind them together

Recently, I've started learning about Polymer and I want to bind together paper-tabs and iron-pages so that when a tab is clicked, the content loads dynamically. After going through the documentation, this is what I have tried: <app-toolbar> ...

What steps should I take to set up an automated polling system for real-time data updates in Angular?

Hello everyone, I am currently delving into the world of Angular and facing a challenge with refreshing service data automatically by making API requests at regular intervals. The focus is on a particular service where I aim to update the shopPreferences f ...

Exploring the power of utilizing multiple classes with conditions in Angular 2+

Can you help me figure out how to use a condition for selecting multiple classes with [ngClass] in Angular? <td> <span [ngClass]="{ 'badge badge-success': server.type === 'PRODUCTION', 'ba ...

In Deno, it is possible to confirm that a variable is an instance of a String

I'm having trouble asserting instances of string in Deno: import { assertInstanceOf } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2642405050405e445e59445e">[email protected]</a& ...