Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting TypeError pipe is not a function while testing angular component using observable with declarative approach using async

When run the tests i am getting i am getting the "TypeError: this.dashboardService.serviceAgents$.pipe is not a function" error.

  • ServiceDashboard page code as below :

     import { Component } from '@angular/core';
     import { ServiceDashboardService } from '../services/service-dashboard.service';
     import { tap } from 'rxjs/operators';
     import { ServiceAgent } from '../interfaces/service-agent';
     @Component({
         selector: 'app-service-dashboard',
         templateUrl: './service-dashboard.page.html',
         styleUrls: ['./service-dashboard.page.css'],
     })
     export class ServiceDashboardPage {
         serviceAgentSlideOptions: any = {
             slidesPerView: 4
         };
         serviceAgents$ = this.dashboardService.serviceAgents$
             .pipe(
                 tap(serviceAgents => {
                     this.serviceAgentSlideOptions.slidesPerView = serviceAgents.length < 4 ? serviceAgents.length : 4;
                 })
             );
         constructor(private dashboardService: ServiceDashboardService) { }
     }  
  • Following is the unit test code for the ServiceDashboardPage.
      import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
      import { async, ComponentFixture, TestBed } from '@angular/core/testing';
      import { ServiceDashboardPage } from './service-dashboard.page';
      import { ServiceDashboardService } from '../services/service-dashboard.service';
      import { ServiceAgent } from '../interfaces/service-agent';
      import { of } from 'rxjs';
      describe('ServiceDashboardPage', () => {
          let component: ServiceDashboardPage;
          let fixture: ComponentFixture<ServiceDashboardPage>;
          let serviceDashboardServiceSpy: ServiceDashboardService;
          beforeEach(async(() => {
              serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService', 
           ['serviceAgents$']);
              TestBed.configureTestingModule({
                  declarations: [ServiceDashboardPage],
                  schemas: [CUSTOM_ELEMENTS_SCHEMA],
                  providers: [
                      { provide: ServiceDashboardService, useValue: serviceDashboardServiceSpy }
                  ]
              })
              .compileComponents();
          }));
          beforeEach(() => {
              fixture = TestBed.createComponent(ServiceDashboardPage);
              component = fixture.componentInstance;
              fixture.detectChanges();
          });
          it('should create', async(() => {
              (serviceDashboardServiceSpy.serviceAgents$ as unknown as 
           jasmine.Spy).and.returnValue(of([] as ServiceAgent[]));
              expect(component).toBeTruthy();
          }));
      });  
like image 762
Kiran Kumar P Avatar asked Oct 18 '25 14:10

Kiran Kumar P


1 Answers

There are a number of issue with your code as written. As it was pointed out in the comments above, you clearly want an Observable in the service, but the command:

serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService', ['serviceAgents$']);

will create serviceAgents$ as a function, and not as an Observable.

But just changing that won't make your code testable, because you will want to change the value that is returned by that Observable and test to see that your component is reacting properly to those changes. For that you will need to refactor your code. The reason for the refactor is because the way you are setting up your Observable in the component by defining it immediately means that it is very hard to change the value and easily test. Simply moving the assignment to ngOnInit() however, will make this much more easily testable.

Then, you need to move fixture.detectChanges() out of the beforeEach() and into the spec itself (the it() function). The reason for this is because fixture.detectChanges() will execute ngOnInit() which we just set up, and we want to more carefully control when that is called.

Finally, you need to set up something to mock your service class - you were trying to use serviceDashboardServiceSpy object to do so, but in this case I prefer to use a mock class rather than a spy object. The reason for this is because you are defining serviceAgents$ within the real service class as a PROPERTY and not as a function. This makes it a little more tricky to test, and setting up a mock class instead of a spy object in my opinion makes this a little easier.

Taking all these things into account, I set up this StackBlitz to show your tests passing.

I also added a couple of tests to show how changing the value in the service Observable changes the associated value within your component.

Here is the .spec from that StackBlitz:

class MockServiceDashboardService {
  get serviceAgents$() {
    return of({length: 2});
  }
}

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

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [ServiceDashboardPage],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            providers: [
                { provide: ServiceDashboardService, useClass: MockServiceDashboardService }
            ]
        })
        .compileComponents();
    }));
    beforeEach(() => {
        fixture = TestBed.createComponent(ServiceDashboardPage);
        component = fixture.componentInstance;
    });
    it('should create', () => {
      fixture.detectChanges();
      expect(component).toBeTruthy();
    });
    it('should have length of 2', () => {
      fixture.detectChanges();
      expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(2);
    });
    it('should have a length of 3', () => {
      let dService = TestBed.get(ServiceDashboardService);
      spyOnProperty(dService, 'serviceAgents$').and.returnValue(of({length: 3}))
      fixture.detectChanges();
      expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(3);
    });
}); 
like image 100
dmcgrandle Avatar answered Oct 21 '25 03:10

dmcgrandle



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!