Search code examples
angularangularfire2

How to unit test by spying on angularfire2 observable?


Trying to stub out angluarfire2 to use in my services. Right now the error I get is: <toHaveBeenCalledWith> : Expected a spy, but got Function.

How do I set things up so that I'm able to make the proper call? I'd like to make things as reusable as possible.

import { TestBed } from '@angular/core/testing';
import { AngularFirestore } from '@angular/fire/firestore';

import { of } from "rxjs";
import { FirebaseService } from './firebase.service';
describe('FirebaseService', () => {

let service: FirebaseService;

let collectionSpy = jasmine.createSpy("collection").and.callFake((path: string) => {
    return of([{
    title: "Example Post",
    body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo dui quis.",
    }]);
});

let afStub: any = {
    collection:()=>{
    return {
        valueChanges:collectionSpy
    }
    }
};

beforeEach(() => {
    TestBed.configureTestingModule({
    providers:[
        { provide: AngularFirestore, useValue: afStub }
    ],      
    })
    service = TestBed.get(FirebaseService); 
});

it('should have a list method', () => {
    afStub.collection('fakeCollection').valueChanges(); //calling directly to see if it works (which it doesn't)
    expect(afStub.collection).toHaveBeenCalledWith('fakeCollection')
});

});

Solution

  • Updated this answer to show Observable returned in valueChanges() method

    You are getting the error because you specifically set up collection as a function rather than a spy with the declaration:

    let afStub: any = {
        collection:()=>{
        return {
            valueChanges:collectionSpy
        }
        }
    };
    

    You could simply declare collection as a spy (with jasmine.createSpy() for example), but I think your problems go a little deeper than just this simple solution, so therefore I put together a Stackblitz to show how I might approach testing something like this. Feel free to fork this and edit with your own service implementation since I just put in a simplified version for now.

    For the spy (to mock/replace AngularFirestore in the service) I used a nested spyObject since I personally find that syntax simpler and more concise, but you could use createSpy and manually build up an object of spies to mock AngularFirestore pretty easily as well. Here is how I declared it:

    const ReturnResult = {
        title: "Example Post",
        body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla commodo dui quis."
    };
    const collectionSpy = jasmine.createSpyObj({
        valueChanges: of(ReturnResult)
    })
    const afSpy = jasmine.createSpyObj('AngularFirestore', {
        collection: collectionSpy
    });
    

    Then in the mock service I created to test, I made the list ()method, since that appears to be what you are trying to test, and put the subscribe you mentioned in the comments below into it. Like this:

    list() {
        this.firebaseService.collection('fakeCollection').valueChanges().subscribe(x=>console.log(x));
    }
    

    Then I updated the spec to the following:

    it('should have a list method', () => {
        service.list();
        expect(collectionSpy.valueChanges).toHaveBeenCalled();
        expect(afSpy.collection).toHaveBeenCalledWith('fakeCollection');
    });
    

    Check out all the details in the Stackblitz. If you click on the "Console" on the very bottom of the test window, you will see that the return is printed on the console log, showing the result was passed through to the .subscribe() method on the Observable.

    I hope this helps.