Testing the use of rxjs fromEvent in Angular while mocking subscriptions

In the Angular component, I have an array of objects representing companies that are provided via @Input(). Upon loading this data, which is obtained from an HTTP request, I assign it to another variable called filteredList, which is used in an *ngFor directive.

The *ngFor loop is used in a selection menu where I have a reference to an HTML Text Input element using @ViewChild. Whenever a keyup event occurs on this input, I filter the filteredList based on the input value. The keyup event is captured using rxJS's fromEvent.

Here's a simplified version of my HTML structure:

<input id="companiesInput" #companyInput>

<mat-selection-list (selectionChange)="listBoxChange($event)">
  <mat-list-option
  *ngFor="let company of filteredList"
    [value]="company"
    [checkboxPosition]="'before'">
    {{company.displayName}}
  </mat-list-option>
</mat-selection-list>

Below is a snippet of my component code which functions as intended:

@Input() companyList: any[] = [];
private tagsSubscription: Subscription;
filteredList: any[] = [];

ngOnChanges(changes: any): void {
  if (changes.companyList.currentValue && changes.companyList.currentValue.length > 0) {
    this.filteredList = this.companyList;
  }
}

ngAfterViewInit(): void {
    const tagsSource = fromEvent(this.companyInput.nativeElement, 'keyup').pipe(
      debounceTime(250),
      distinctUntilChanged(),
      switchMap((ev: any) => {
        return of(ev.target.value);
      })
    );

    this.tagsSubscription = tagsSource.subscribe((res: any) => {
      res = res.trim();
      if (res.length > 0) {
        this.filteredList = this.companyList.filter((company) => company.displayName.indexOf(res) > -1);
      } else {
        this.filteredList = this.companyList;
      }
    });
  }

Everything functions correctly, but I am facing challenges when attempting to test my code. Here is my testing script:

describe('tagsSubscription', () => {
    it('should filter the available list of companies returned', () => {
      const dummyArray = [
        { id: 1, displayName: 'safety io' },
        { id: 2, displayName: 'msa' },
        { id: 3, displayName: 'acme' }
      ];

      component.companyList = dummyArray;

      fixture.detectChanges();
      component.ngOnChanges({ companyList: { currentValue: dummyArray } });

      const fromEventSpy = spyOn(rxjs, 'fromEvent').and.returnValue(() => rxjs.of({}));
      const companiesInput = element.querySelector('#companiesInput');

      element.querySelector('#companiesInput').value = 'ac';
      companiesInput.dispatchEvent(generateKeyUpEvent('a'));
      fixture.detectChanges();

      companiesInput.dispatchEvent(generateKeyUpEvent('c'));
      fixture.detectChanges();

      expect(component.filteredList.length).toEqual(1);
    });
  });

Upon logging outputs, I noticed that in my test scenario, the fromEvent is not triggered, leading to the failure of tagsSource.subscribe. I tried mocking fromEvent in my test by adding the following line at the beginning of the event:

const fromEventSpy = spyOn(rxjs, 'fromEvent').and.returnValue(() => rxjs.of('ac'));

Unfortunately, this approach did not solve the issue either. If anyone has experience testing fromEvent Observables in Angular and knows how to properly run tests in such cases, I would greatly appreciate some guidance on getting my testing environment up and running smoothly.

Answer №1

Suppose we have a scenario where tagSource is a component property:

@Input() companyList: any[] = [];
private tagsSubscription: Subscription;
filteredList: any[] = [];
tagSource: any;

ngAfterViewInit(): void {
    this.tagsSource = fromEvent(this.companyInput.nativeElement, 'keyup').pipe(
      debounceTime(250),
      distinctUntilChanged(),
      switchMap((ev: any) => {
        return of(ev.target.value);
      })
    );
 }

In order to test this property, create a fake input element and set it as the nativeElement:

it('ngAfterViewInit():', fakeAsync(() => {
    const mockInputElement = document.createElement('input');
    component.companyInput = {
        nativeElement: mockInputElement
    };
    component.ngAfterViewInit();
    mockInputElement.value = 'example' // provide input for testing
    mockInputElement.dispatchEvent(new Event('keyup'));
    tick(250); // simulate debounceTime
    expect(component.tagSource).toEqual('example');
}));

You can follow this approach to validate your component.

Answer №2

If you're dealing with a ViewChild, simply activate it.

const input = fixture.debugElement.query(By.css('#companiesInput'));
input.triggerEventHandler('keyup', {/* some data */});

If the element is an input field - simulate it and incorporate 2 additional methods

let handler: Function | undefined;

companyInput.nativeElement.addListener = (eventName: string, eventHandler: Function) => {
  if (eventName === 'keyup') {
    handler = eventHandler;
  }
};
companyInput.nativeElement.removeListener = () => {};

// execute your code to subscribe

if (handler) {
  handler('value1');
  handler('value2');
  handler('value3');
  handler('value4');
}

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

Error: The code is unable to access the '0' property of an undefined variable, but it is functioning properly

I am working with two arrays in my code: bookingHistory: Booking[] = []; currentBookings: any[] = []; Both arrays are populated later in the code. The bookingHistory array consists of instances of Booking, while currentBookings contains arrays of Booking ...

Tips for Implementing a Button Click Sound in Ionic 2

I've noticed that many native apps have a distinct click sound when buttons are pressed. Is there a way for me to add this same feature to all buttons in Ionic 2? ...

What is the process for retrieving the parent component from projected content?

One interesting aspect to explore is the varying behaviors of input based on its 'parent' element. The structure I am referring to is as follows: In my first example, the input is nested within the app-chip-list component. APP COMPONENT HTML & ...

What are the steps to implement SignalR Stream in an ASP.NET Core Angular application?

Attempting to utilize SignalR stream for sending real-time data to clients and displaying it on an Angular component's template. Below is a snippet of my code, where this function is linked to a button (click) event in the template: getMessage(m ...

TS2322: Subclass missing property, yet it still exists

In my project, I have defined two Angular 4 component classes. The first class, referred to as the superclass: export class SectionComponent implements OnInit { slides: SlideComponent[]; constructor() { } ngOnInit() { } } And then there&apo ...

npm is unable to install a forked git repository in its current state

Attempting to install a customized version of ng2-smart-table on my application, but npm seems to be struggling with the process. I've experimented with various commands such as npm install git+http://github.com/myusername/ng2-smart-table.git npm i ...

Troubleshooting: Authentication guard not functioning properly in Angular 2 due to HTTP request

As I work on implementing a guard for certain routes in my application, I face an issue. To grant access to the route, my intention is to send an HTTP request to my express backend API and check if the user's session exists. I have explored various e ...

Angular's two-way binding feature does not seem to be updating the value for date

In my Angular - Dotnetcore 2.0 form, users are required to update their demographic information including their birth date. Initially, I was using the following code snippet to display the birthdate: <input id="dateOfBirth" type="date" class="form-cont ...

Changing HTML tags programmatically in Angular while inheriting a template

In my project, I have a Component called DataGrid that represents a table with expandable rows. Each row can be expanded to show a child DataGrid table, which is similar to the parent DataGrid component. To simplify this setup, I created a base class DataG ...

What is the best way to integrate ag-grid with Observable in Angular 2?

After conducting extensive research on the Internet, I am still struggling to connect the pieces. My angular2 application utilizes an Observable data source from HTTP and I am attempting to integrate ag-grid. However, all I see is a loading screen instead ...

Actions in ASP Core to automatically install necessary tools for Angular2 before and after publishing, followed by the build

Our .NET Core project utilizes Angular2 as the frontend client, housed in a Frontend directory within our solution. The Frontend directory includes package.json and angular-cli.json to keep frontend separate from the rest of the .NET project. When ng buil ...

Angular 4: Unhandled error occurred: TypeError - X does not exist as a constructor

I am currently developing a project in Angular 4, and I encountered an error while running the application. The specific error message is as follows - ERROR Error: Uncaught (in promise): TypeError: index_1.EmployeeBase is not a constructor TypeError: in ...

Error message: "Angular 2 encountered an issue while attempting to access the property 'nativeElement' of an

Hello, I'm having issues retrieving data (which has been extracted after using ngFor) from the HTML using viewChildren and elementRef. I keep receiving the error message: Cannot read property 'nativeElement' of undefined Can someone please ...

Tips for deploying an Angular 6 application on a Node server

I am working on an Angular 6 application that needs to be served by a Node server running on port 8080. After some research, I found that adding a server.js file with certain configurations can help achieve this. However, I am unsure about how to modify th ...

Utilizing Angular to render JSON data on the screen

Currently, I have my data saved in a database and am retrieving it in Angular. The problem arises when I attempt to display the data as I encounter an error. All I want is to exhibit the question in HTML using ngFor. Being new to Angular, any advice or gui ...

Angular: Retrieve the source after navigating

Hello there, I am facing a simple problem. I have 2 components navigating to 1 component and in that one component, I need to distinguish which component the navigation came from so I can take appropriate action (such as refreshing a list). The issue is t ...

Angular - Display shows previous and current data values

My Angular application has a variable called modelResponse that gets updated with new values and prints them. However, in the HTML, it also displays all of its old values along with the new ones. I used two-way data binding on modelResponse in the HTML [( ...

What methods can be used to protect an application with spring boot and angular 8 without requiring users to log in?

I am looking to enhance security for an application that leverages angular 8 and spring boot 5. Currently, the application is free form and does not require login to access the UI. Although I have implemented CSRF protection, it is still possible for som ...

What methods can I use to integrate a Google HeatMap into the GoogleMap object in the Angular AGM library?

I am trying to fetch the googleMap object in agm and utilize it to create a HeatMapLayer in my project. However, the following code is not functioning as expected: declare var google: any; @Directive({ selector: 'my-comp', }) export class MyC ...

The Angular Animation feature seems to function properly only upon the initial click

Why does the animation (translateX) only work on the first click in the stackblitz example provided? How can I make it work every time? Thanks for any help! https://stackblitz.com/edit/angular-ivy-p9strz ...