Exploring Angular 11 Testing: Troubleshooting Async Issues in Jest with RxJS Timer

I encountered an issue while working on a test where I received the following error message: "Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout."

Below is the code for the test:

beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [DashboardTimerComponent, FormatTimePipe],
        imports: [BrowserAnimationsModule, ReactiveFormsModule],
        providers: [FormBuilder],
      }).compileComponents();
    }),
  );

  beforeEach(() => {
    fixture = TestBed.createComponent(DashboardTimerComponent);
    component = fixture.componentInstance;
  });

  it('should stop counter and emit event', fakeAsync(() => {
    spyOn(component.stopped, 'emit');

    component.stopRequested = true;
    component.runningTimer = { timer: 19 };
    fixture.detectChanges();
    

    const button = fixture.debugElement.nativeElement.querySelector('#stop');
    button.click();

    expect(component.timer).toBeNull();
    expect(component.stopped.emit).toHaveBeenCalled();
  }));

This is the component:

@Component({
  selector: 'dashboard-timer',
  templateUrl: './dashboard-timer.component.html',
  providers: [DashboardTimerService],
  animations: [fadeInAnimation],
})
export class DashboardTimerComponent {
  @Input() projects: any;
  @Input() runningTimer: any = null;

  @Output() started = new EventEmitter();
  @Output() stopped = new EventEmitter();

  public form: FormGroup;

  public timer: number = null;

  public stopRequested: boolean = false;

  public counter: Subscription;

  private project: FormControl = new FormControl('');

  private note: FormControl = new FormControl('');

  constructor(
    private dashboardTimerService: DashboardTimerService,
    private fb: FormBuilder,
  ) {}

  ngOnInit() {
    // initialize form
    this.form = this.fb.group({
      project: this.project,
      note: this.note,
    });

    if (this.runningTimer) {
      this.timer = this.runningTimer.timer;
      this.form.controls['project'].setValue(this.runningTimer.project || '');
      this.form.controls['note'].setValue(this.runningTimer.note || '');

      this.counter = this.dashboardTimerService
        .getCounter()
        .subscribe(() => this.timer++);
    }
  }

  /**
   * check if stop requested, stop counter, emit stop to parent component
   */
  stop(): void {
    if (this.stopRequested === false) {
      this.stopRequested = true;

      setTimeout(() => {
        this.stopRequested = false;
      }, 5000);
      return;
    }
    this.stopRequested = false;

    this.counter.unsubscribe();
    this.stopped.emit();
    this.timer = null;
  }
}

The error appears to be related to this service:

import { Injectable } from '@angular/core';
import { timer } from 'rxjs';

@Injectable()
export class DashboardTimerService {
  getCounter() {
    return timer(0, 1000);
  }
}

It seems like the timer is still running even after unsubscribing from it in the component. Any suggestions on how to resolve this would be greatly appreciated!

Thank you!

Answer №1

After reviewing your stackblitz, it appears that the component is responsible for providing the service, creating its own instance instead of using the mock value provided in the TestBed providers.

My initial question is whether the service truly needs to be provided on the component itself.

If it does, there are two possible approaches:

Given that your dataservice utilizes a timer set at 1000, you will need to wait for that specific duration. Using fakeAsync combined with tick would be suitable for this scenario.

it("should stop counter and emit event", fakeAsync(() => {
    spyOn(component.stopped, "emit");

    fixture.detectChanges();

    const button = fixture.debugElement.nativeElement.querySelector("#stop");
    button.click();

    tick(1000); // -> wait for the promise to resolve
    fixture.detectChanges();

    expect(component.timer).toBeNull();
    expect(component.stopped.emit).toHaveBeenCalled();
  }));

Alternatively, you can override the injected service after the component has been created along with its service instance.

it("should stop counter and emit event", fakeAsync(() => {
    spyOn(component.stopped, "emit");

    // override the actual injected service with mock values here
    (<any>TestBed.inject(DashboardTimerService)).getCounter = jest.fn().mockReturnValue(of(0))

    fixture.detectChanges();

    const button = fixture.debugElement.nativeElement.querySelector("#stop");
    button.click();

    tick(); // it is recommended to use fakeAsync and tick when dealing with observables
    fixture.detectChanges();

    expect(component.timer).toBeNull();
    expect(component.stopped.emit).toHaveBeenCalled();
  }));

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

The await function does not pause execution before proceeding to the next line of code

Here is the code I've been working on: async function main(collection, token, symbol) { await collection.deleteMany({}); const priceHistory = await makePriceHistoryRequest(token, symbol, slow*2); await insertPriceHistory(collection,priceHistory) ...

Ways to verify that window.open is being invoked from a React component

In my React component, I have a set of nested components, each with its own checkbox. The state hook rulesToDownload starts as an empty array and dynamically adds or removes IDs based on checkbox selection. Upon clicking the 'download' button, t ...

Sending a message from a Vue.js application to Contact Form 7 using the Wordpress REST API - a step-by-step guide

I recently added Contact-Form-7 to my WordPress admin panel, which generated an API Endpoint for me at http://localhost/wordpress/wp-json/contact-form-7/v1/contact-forms Attempting to send a POST request to this endpoint using the following code: data() ...

The system encountered difficulty handling a recursive structure

I am facing a challenge with a recursive JSON structure that needs to be stored as a series of maps with keys. The structure comprises flows and subflows that have references to each other. Here are the type declarations, noting that the issue lies in the ...

Steps for creating a Foundation 6 accordion

Can anyone assist me in creating a Foundation 6 accordion? I tried to follow the code from the documentation, but it's quite confusing. I seem to be missing something and can't figure out what. Here is the HTML code from the docs: <ul class= ...

Trouble arises when attempting to modify a property inherited from a parent class in TypeScript while

I've defined a base class like the following: import Vue from "vue"; class ComponentBase extends Vue { constructor() { super(); this.left = 100; this.top = 100; this.isSelected = false; } public left: numb ...

What is the method for updating a 'Signal' within an 'Effect'?

Working with signals in Angular 17, I encountered an issue while trying to update the value of a signal. The error message that I received is as follows: NG0600: Writing to signals is not allowed in a `computed` or an `effect` by default. Use `allowSignalW ...

The unprojection of the vector in Threejs from this camera has not been defined

Why am I encountering a "function is undefined" error for vector.unproject even though it is clearly mentioned in the documentation? You can find it here: (one of the last items) Even after console logging it, the function still returns as undefined, des ...

Error encountered when attempting to use _id from a different collection due to a duplicate key

In my database, I currently have 2 collections set up. The first one is a 'Users' collection (which is functioning properly), and the other is a 'Rooms' collection (both designed for a chat application). I am looking to ensure that e ...

What is the best way to show the current value of a checkbox element in the future?

I want to add multiple checkboxes using this method: $(".btn-sample").on("click", function() { $(".container").append( '<input class="check-sample" type="checkbox" value="'+check_value+'"/>' ); }); The issue I' ...

Tips for updating the selected date format in a Material Table component using React

In the context of using a material table in React, the columns set up are as follows: columns={[ { title: 'Name', field: 'name', type: 'string', }, ...

Encountering a blank webpage displaying a warning that reads, "The use of 'event.returnValue' is outdated

Although this issue has been discussed before and was previously considered a bug, I am currently using jQuery v1.11.0, which should have resolved it. The problem I am encountering is that when my page loads, there is supposed to be a slide-in effect (as a ...

Angular 2 Cordova application experiencing challenges with updating member variables that are not reflecting changes in the associated template

In my Cordova app with Angular 2, I am facing an issue where the @Component decorated AppComponent class member variable is not updating in the view template as expected. I have noticed that the first update to the member variable gets rendered in the vie ...

Neglect to notify about the input text's value

Having trouble retrieving the text from a simple <input id="editfileFormTitleinput" type="text>. Despite my efforts, I am unable to alert the content within the input field. This is the code snippet I've attempted: $('#editfileFormTitleinp ...

Show brief tags all on one line

This image showcases the functionality of the site, specifically in the "Enter a code" column where users can input data using TagsInput. I am seeking to enhance this feature by ensuring that shorter tags are displayed on a single line. While I am aware th ...

Customized Bootstrap Dropdown with the ability to add text dynamically

I am working on a Bootstrap dropdown menu that allows users to generate expressions by clicking on the menu items. For example, when the "Action" item is clicked, it should insert {{Action}} into the textbox. The user can then type something like "Hello" a ...

Is there a way to stop the continuous loading of AJAX requests?

For weeks, I have been facing a persistent issue that I've attempted to solve in various ways. The problem lies in my ajax search function. Every time I initiate a search, it continues loading multiple times. Surprisingly, the more searches I perform ...

Utilizing Node.js and Eclipse to Transfer MongoDB Data to Browser

I am working on a project where I need to display data fetched from MongoDB using Node.js in a web browser. The data is stored in the 'docs' object and I want to pass it to an EJS file so that I can insert it into a table: var express = require( ...

A guide to efficiently reusing parameter definitions in functions using JSDoc

Currently, I have HTTP handlers set up in my express application. I want to use JSDoc annotations to document these handlers in a reusable manner. Here is what I currently have: /** * * @param {functions.Request} req * @param {functions.Response} res ...

Is it necessary to download all npm packages when starting a new project?

I recently started learning about npm packages and node. I noticed that when installing packages, they create numerous folders in the "node modules" directory. This got me thinking - when starting a new project, do I need to re-install all these packages ...