'Jasmine unit test Angular service with nested spies

I´m trying to test this Angular service which uses af.firestore.collection, not just af.collection, as shown below, so to be able to access the onSnapshot command.

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Client } from '../models/client';

@Injectable({ providedIn: 'root' }) export class ClientService {

  constructor(private cloud: AngularFirestore){}

  Clients = this.cloud.firestore.collection('clients');
  clients = [] as Client[];
  selectedClient = {} as Client;

  getClients(){
    this.Clients.onSnapshot(clientSnapShot => {
      clientSnapShot.docChanges().forEach(change => {
        let client = change.doc.data() as Client;
        let index = this.clients.findIndex(us => us.id === client.id);
        if(change.type === 'added' && index === -1) this.clients.push(client);
        if(change.type === 'modified') this.clients.splice(index, 1, client);
        if(change.type === 'removed') this.clients.splice(index, 1);
      });
      this.clients = this.clients
        .sort((a,b) => a.name > b.name ? 1 : a.name < b.name ? -1 : 0)
        .sort((a,b) => a.active > b.active ? -1 : a.active < b.active ? 1 : 0)
    });
  }

}

When testing it with Karma, I tried to use nested jasmine.createSpyObj method. This is the spec.ts file:

import { TestBed } from "@angular/core/testing";
import { AngularFirestore, AngularFirestoreModule } from "@angular/fire/firestore";
import { of } from "rxjs";
import { ClientService } from "./client.service";

describe('ClientService', () => {
  let clientServiceMock: any;
  let angularFirestoreMock: any;
  let fsCollecton: any;

  beforeEach(() => {
    clientServiceMock = jasmine.createSpyObj('ClientService', ['getClients']);
    
    angularFirestoreMock = jasmine.createSpyObj('AngularFirestore', ['firestore', 'collection']);
    fsCollecton = jasmine.createSpyObj('collection', ['onSnapshot']);
    angularFirestoreMock.firestore.collection.and.returnValue(fsCollecton);      // here´s the problematic line.
    fsCollecton.onSnapshot.and.returnValue(of([]));

    TestBed.configureTestingModule({
      imports: [
        AngularFirestoreModule
      ],
      providers: [
        { provide: ClientService, useValue: clientServiceMock },
        { provide: AngularFirestore, useValue: angularFirestoreMock }
      ]
    });

    clientServiceMock = TestBed.inject(ClientService);
  });

  it('should be created', () => {
    expect(clientServiceMock).toBeTruthy();
  });

  it('should call collection and onSnapshot methods', () => {
    clientServiceMock.getClients();

    expect(angularFirestoreMock.firestore.collection).toHaveBeenCalledTimes(1);
  })

});

I know how to test fs.collection method, but when fs.firebase.collection is used, it seems Karma doesn´t recognize it, and this is the error I have:

TypeError: Cannot read properties of undefined (reading 'and')
    at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/webpack:/src/app/services/client.service.spec.ts:16:47)
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:400:1)
    at ProxyZoneSpec.push.3775.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:301:1)
    at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:399:1)
    at Zone.run (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:160:1)
    at runInTestZone (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:581:1)
    at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:596:1)
    at <Jasmine>

which means that angularFirestoreMock.firestore.collection is not recognized. The spec.ts file was done by following the guide of Lars Bilde in https://www.youtube.com/playlist?list=PL8jcXf-CLpxolmjV5_taFP0c5LyCveDF1.

I couldn't find how to solve this in the official documentation, nor in any tutorials, and I didn't find a similar question on this site.

Any help is appreciated!



Solution 1:[1]

The problem is that you did not define a return value for angularFirestoreMock.firestore, so angularFirestoreMock.firestore.collection is undefined.

To fix it, add a line like angularFirestoreMock.firestore.and.returnValue(...).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 slim