Implementing NgbModule.forRoot() within a component is leading to test failures

While utilizing Tooltips and Modals within a nested component, I encountered an issue in one specific component during testing. In my spec file, I included the NgbModule.forRoot() import in the testing module which caused many unit tests to fail with the following error:

TypeError: this._unregisterListenersFn is not a function
        at NgbTooltip.ngOnDestroy

The problem seems isolated to this particular component, even though the setup works fine elsewhere. Attempting to import the Tooltip/Modal modules separately along with their respective providers did not resolve the error, as DI errors persisted without calling forRoot().

I am using Angular CLI for bundling and testing purposes.

This faulty component is the only one causing issues for my tests, leaving me puzzled about the root of the problem.

Below is an excerpt from the spec file related to this component:

/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule, NgbTooltipModule, NgbTooltipConfig, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbModalStack } from '@ng-bootstrap/ng-bootstrap/modal/modal-stack';

import { ListItemComponent } from './list-item.component';
import { VideoPlayerService } from '../../../video-player';
import { CalendarRoutingService } from '../../calendar-routing.service';

describe('ListItemComponent', () => {
  let component: ListItemComponent;
  let fixture: ComponentFixture<ListItemComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        ListItemComponent
      ],
      imports: [RouterTestingModule, NgbModule.forRoot()],
      providers: [
        VideoPlayerService,
        CalendarRoutingService,
        // NgbModalStack,
        // NgbTooltipConfig
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ListItemComponent);
    component = fixture.componentInstance;
    component.item = { records: [] };
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Answer №1

While working on a solution, I encountered an issue with NgbTooltip when it was running within a test fixture. I found a workaround by globally redefining the ngOnDestroy method of NgbTooltip as shown below:

NgbTooltip.prototype.ngOnDestroy = function () {
    this.close();
    //this._unregisterListenersFn();
    this._zoneSubscription.unsubscribe();
};

By commenting out the third line, I was able to prevent errors from appearing in my unit tests. It's a bit of a hack, but it does the job for testing purposes. It seems that the function is not initialized correctly in ngOnInit() when running in a test fixture.

I also attempted to override the NgbTooltip directive using overrideDirective(), but it seemed like the original directive was always being called regardless.

To pinpoint the exact error, I added the following line to my unit test spec:

afterEach(() => {
  fixture.destroy();
});

This allowed me to see the actual exception that was occurring:

TypeError: this._unregisterListenersFn is not a function
at NgbTooltip.webpackJsonp.../../../../@ng-bootstrap/ng-bootstrap/tooltip/tooltip.js.NgbTooltip.ngOnDestroy (http://localhost:9876/_karma_webpack_/vendor.bundle.js:4522:14)
at callProviderLifecycles (http://localhost:9876/_karma_webpack_/vendor.bundle.js:103669:18)
at callElementProvidersLifecycles (http://localhost:9876/_karma_webpack_/vendor.bundle.js:103638:13)
at callLifecycleHooksChildrenFirst (http://localhost:9876/_karma_webpack_/vendor.bundle.js:103622:17)
at destroyView (http://localhost:9876/_karma_webpack_/vendor.bundle.js:104948:5)
at callViewAction (http://localhost:9876/_karma_webpack_/vendor.bundle.js:105094:13)
at execComponentViewsAction (http://localhost:9876/_karma_webpack_/vendor.bundle.js:105006:13)
at destroyView (http://localhost:9876/_karma_webpack_/vendor.bundle.js:104947:5)
at callViewAction (http://localhost:9876/_karma_webpack_/vendor.bundle.js:105094:13)
at execComponentViewsAction (http://localhost:9876/_karma_webpack_/vendor.bundle.js:105006:13)

Answer №2

My recommendation would be to simply stub it within the beforeEach function:

// resolve 'Issue during clean up of component'
NgbTooltip.prototype.ngOnDestroy = jasmine.createSpy('ngOnDestroy');
(NgbTooltip.prototype.ngOnDestroy as jasmine.Spy).and.stub();

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

Changing the Size of a Map using react-simple-maps within a React Application

I'm having difficulties with displaying a France map using react-simple-maps. The issue I'm facing is that the map appears too small despite my efforts to adjust it through CSS, width and height attributes, and by utilizing ZoomableGroup. Unfortu ...

VSCode mistakenly detecting Sequelize findOne and findAll return type as any inferences

I have a model defined using Sequelize as shown below: import { Sequelize, Model, BuildOptions, DataTypes } from 'sequelize'; interface User extends Model { readonly id: string; email: string; name: string; password_hash: string; reado ...

What is the best approach to manage dynamic regex values for input types within ngFor loop?

In my project, I have an array of objects containing details for dynamically generating input fields. I have successfully implemented the dynamic input field generation based on the type received from an API. However, I am facing a challenge with matching ...

Angular: Retain the original value of the observable

When making HTTP requests to a backend and receiving data, I use an observable stream and subscribe to it in my HTML template using async pipe. However, I am facing an issue. I need to continuously send multiple requests by clicking a button, but I want th ...

View the material expansion panel by scrolling without turning off the animation

Whenever a MaterialExpansionPanel opens, I use the scrollIntoView method. opened(i) { setTimeout(() => this.panels.toArray()[i].nativeElement.scrollIntoView({ behavior: 'smooth' } )); } Although it works, if the animation ([@. ...

Tips for personalizing your Compodoc Angular documentation

I've been experimenting with adding extra side navigation menus to the current compodoc documentation. Here's an example of how I tried to accomplish this: menu-wc.js <li class="link"> <a href="dependencies.html" data-type="chapte ...

Struggle to deduce the generic parameter of a superior interface in Typescript

Struggling with the lack of proper type inference, are there any solutions to address this issue? interface I<T> {}; class C implements I<string> {}; function test<T, B extends I<T>>(b: B): T { return null as any; // simply for ...

Calculate the difference and sum of time values with varying signs in JavaScript

-12:00 - 5:30 => -6:30 -2:00 - 5:30 => 3:30 00:00 - 5:30 => -5:30 6:00 - 2:30 => 3:30 I am interested in subtracting time with both positive and negative indices. let myCountries = [ { countryName: "NewZealand", ...

Learning to display an error message by clicking the send button in an Angular 2 application

Despite my best efforts, I have struggled to find a solution. I have searched various websites, but everywhere they only show validations on touch or hide the button until the form is valid. How can I implement validations by simply clicking a button? Here ...

Troubleshooting: Difficulty assigning a value to a nested object in Angular

I'm a beginner in Angular and TypeScript so please forgive me if this question seems silly. I am attempting to store the value of a returned object into a nested property within an object with a type of any. Unfortunately, it is not allowing me to do ...

Angular 4: Utilizing reactive forms for dynamic addition and removal of elements in a string array

I am looking for a way to modify a reactive form so that it can add and delete fields to a string array dynamically. Currently, I am using a FormArray but it adds the new items as objects rather than just simple strings in the array. Here is an example of ...

What is the best way to differentiate the handling of a 401 Unauthorized response from other errors within an Angular 8 service that utilizes RxJS?

My REST API implementation requires an access token for user identification with each call. If the token is missing or expired, the endpoint returns a 401 UNAUTHORIZED response. There are instances where I make API calls using a timer in my service class: ...

The `@ViewChild` reference cannot be found

My main challenge is toggling a @ViewChild element using an *ngIf, followed by invoking a native event. This snippet shows my HTML element, tagged with #audioPlayer for extracting it through @ViewChild. <audio #audioPlayer *ngIf="convers ...

In Typescript, it is not possible to use generics as a function parameter in a

Looking for a solution regarding passing the union of two specific samples of generics to a function. The function in question is as follows: function myCommonFunc<T>({ data, render, }: { data: T; render: (data: T) => number; }) { return ...

Updating Angular libraries can enhance the performance and functionality of

Within our organization, we rely on several custom Angular libraries for the majority of our projects. The issue arises when we need to update our codebase to a new version of Angular, as it requires updating all libraries simply to ensure compatibility ...

What are the most effective methods for handling asynchronous operations in Angular 9?

As soon as I upgraded from version 8 to version 9, the asynchronous logic in my HTML code stopped functioning. For example: <div id="app" *ngIf="(applicationsList$ | async) as applicationsList"> <app-search-filter [(applicationsList)]="appli ...

Challenges with Primeng Component UI

I am currently working with PrimeNG components, but I'm facing issues with the UI display on the web browser. At this moment, I need to showcase a static dropdown. I have referred to PrimeNG for guidance. Below is the code snippet to implement that c ...

How to display a page outside the router-outlet in angular 4

I'm currently developing an angular 4 application and I am trying to figure out how to load the login.page.ts outside of the router-outlet This is what my home.component.html file looks like: <div class="container"> <top-nav-bar></ ...

Using observables rather than promises with async/await

I have a function that returns a promise and utilizes the async/await feature within a loop. async getFilteredGuaranteesByPermissions(): Promise<GuaranteesMetaData[]> { const result = []; for (const guarantees of this.guaranteesMetaData) { ...

Combining TypeScript into HTML resulted in an error: Uncaught ReferenceError clickbutton is not defined

Attempting to create a basic CRUD frontend without the use of any frameworks. I am encountering an issue when trying to include a TypeScript file (index.ts) in my index.html, as the functions called within it are showing as undefined. I understand that bro ...