Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - Testing / mocking subscriptions that use rxjs fromEvent

in my Angular component I have an object array of companies which is provided as via @Input(): When this is loaded (as it is derived from a HTTP request) I take the value and assign it to another variable called filteredList that I use for an *ngFor.

The *ngFor is used in a select menu and I have a @ViewChild which is an HTML Text Input that when a keyup event happens I take the value of the input and filter the filteredList. I am capturing the keyup event using rxJS fromEvent

To simplify this is my HTML

<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>

and here is some of my component code... all works well!!

@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 works great, but I need to test my code and this is where I fall short. Here is my test

describe('tagsSubscription', () => {
    it('should filter the available list of companies returned', () => {
      const dummyArray = [
        { id: 1, displayName: 'saftey 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);
    });
  });

From logging out I can see that in my test the fromEvent is never raised and thus tagsSource.subscribe is never ran. I am doing something wrong, I thought about mocking the fromEvent in my test by adding the following line to the top of my event, like so

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

this doesn't work either. Has anyone any experience of how to test fromEvent Observables with Angular and perhaps can tell me how I can get my test working?

like image 870
Mike Sav Avatar asked Sep 09 '26 11:09

Mike Sav


1 Answers

Let's assume, if tagSource is a property of the component,

@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);
      })
    );
 }

To test this property, create a mock input element and assign it to the nativeElement:

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

You can use this logic to test your component.

like image 130
suyashpatil Avatar answered Sep 12 '26 02:09

suyashpatil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!