Error: InvalidPipeArgument: 'function () { return fn.apply(this, arguments); }' for pipe 'AsyncPipe' Angular 7 Unit testing

Viewed 1899

I'm writing unit test cases in Angular 7 in this process got stuck to create unit test for a service variable 'isLoading'.

here is my app component html file:

<router-outlet></router-outlet>


<div *ngIf="guestUserService.isLoading | async"  class="loaderdiv">
  <mat-spinner class="loader"></mat-spinner>
</div> 

The service file:

export class GuestUserService {

  public isLoading: any = new BehaviorSubject(false);

}

The unit test case:

 fdescribe('AppComponent', () => {
         let guestUserService: any;
         let guestUserServiceSpy: any;

          beforeEach(async(() => {
            guestUserService = jasmine.createSpyObj('GuestUserService', ['isLoading']);
            TestBed.configureTestingModule({
              providers: [
                { provide: GuestUserService, useValue: guestUserService }]
            }).compileComponents();
}));

 it('should render loader if isLoading true', () => {
    guestUserServiceSpy = guestUserService.isLoading.and.returnValue(true);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(guestUserService).toBeDefined();
    expect(guestUserServiceSpy).toBeDefined();
    expect(guestUserService.isLoading).toBeTruthy();
    expect(fixture.debugElement.query(By.css('.loader'))).toBeDefined();
    expect(compiled.querySelector('mat-spinner')).toBeTruthy();
  });

Getting error:

Error: InvalidPipeArgument: 'function () { return fn.apply(this, arguments); }' for pipe 'AsyncPipe'

Is there any proper way to create unit test case for this type of scenarios.

2 Answers

There are several problems.

First, you're shooting yourself in the foot by using the type any. By doing this, the compiler let's you do things that don't make sense. And you also make the code harder to read for yourself. The isLoading property is supposed to be an Observable<boolean>, since that's what the template expects. So use that correct type instead of any.

Second, this line:

jasmine.createSpyObj('GuestUserService', ['isLoading'])

creates an object with a function named isLoading. But your actual service has a property of typeObservable`` named isLoading, and that's what your template expects, hence the error: tha async pipe needs an observable as input, but you're passing it isLoading, which is a function in your test, and not an observable (see how using the right type helps reasoning about the errors you get?).

And BTW, this function returns a boolean true, instead of an Observable<boolean>

You could define a simple object instead of using jasmine to create your fake service:

const fakeService = {
    isLoading: of(true)
}

Or you could use jasmine but use its spyOnProperty function.

Or you could use BehaviorSubject<boolean> as the type of isLoading and thus use the real service implementation and simply make the subject emit true.

Just some extra information on creating Jasmine spy objects to fake services with get properties.

To create a spy object with get properties, the methods and properties need to be passed into the constructor separately:

createSpyObj('ExampleService’, { methods }, { get properties }); 

Example

The following 'Example' service has two methods and two get properties:

export class ExampleService {
  // properties
  public get exampleProperty1(): Observable<boolean> {
    // implementation...
  }
  public get exampleProperty2(): Observable<Number> {
    // implementation...
  }

  // methods
  public exampleMethod1(): void {
    // implementation...
  }
  public exampleMethod2(): void {
    // implementation...
  }
}

Create a Jasmine spy object for the example service like this:

fakeExampleService = jasmine.createSpyObj<ExampleService>(
  'FakeExampleService',
  {
    // pass in the methods here...
    exampleMethod1: undefined,
    exampleMethod2: undefined
  },
  {
    // pass in the properties here...
    exampleProperty1: of(false),
    exampleProperty2: of(1),
  }
);
Related