miguelsmuller
miguelsmuller

Reputation: 59

Angular and Jasmine Test firebase auth with google provider cannot read property

I'm starting with unit testing and it's hard to test a method that login using firebase auth. I've tried it in many ways and tested it in several means as you can see in the code comments and in most cases it always returns:

TypeError: Cannot read property 'GoogleAuthProvider' of undefined

I used the ng-mocks library to mock AngularFireAuth.

the project can be seen in this branch of the project on github branch

this is the method i want to test:

login(): Observable<IUser> {
    const provider = new firebase.auth.GoogleAuthProvider();
    const responseAuth = from(this.fireAuthService.signInWithPopup(provider)).pipe(
      map((fireUser) => {
        const objUser: IUser = {
          uid: fireUser.user?.uid!,
          name: fireUser.user?.displayName!,
          email: fireUser.user?.email!,
          emailVerified: fireUser.user?.emailVerified!,
          photoURL: fireUser.user?.photoURL!,
          provider: fireUser.user?.providerId!,
        };
        return objUser;
      }),
      catchError(() => {
        throw new Error('error in the logout process');
      })
    );

    return responseAuth;
  }

this is the test.

it('should executed login method with success', fakeAsync(() => {
    const googleSignInMethod = spyOn(fireAuthService, 'signInWithPopup');

    service.login().subscribe((data) => {
      expect(googleSignInMethod).toHaveBeenCalled();
    });

    // const googleSignInMethod = spyOn(fireAuthService, 'signInWithPopup').and.callThrough();
    // service.login().subscribe((data) => {});
    // expect(googleSignInMethod).toHaveBeenCalled();

    // service.login().subscribe((data) => {
    //   let spy = spyOn(fireAuthService, 'signInWithPopup');
    // expect(spy).toHaveBeenCalledWith(provider);
    //   expect(spyOn(fireAuthService, 'signInWithPopup')).toBeTruthy();
    // });
    // spy = spyOn(service, 'isAuthenticated').and.returnValue(false); (3)
    // expect(component.needsLogin()).toBeTruthy();
    // expect(service.isAuthenticated).toHaveBeenCalled(); (4)
  }));

Upvotes: 2

Views: 763

Answers (1)

satanTime
satanTime

Reputation: 13574

The problem with the test is that in testing environment new firebase.auth.GoogleAuthProvider(); fails due to 0 existing apps.

There are 2 solutions how to fix it:

  1. Provide a testing app
  2. Avoid usage of new in the method and make the dependency to be injected.

The 2nd way is the preferred one:

export const FIREBASE_AUTH_PROVIDER = new InjectionToken('FIREBASE_AUTH_PROVIDER', {
  providedIn: 'root',
  factory: () => new firebase.auth.GoogleAuthProvider(),
});

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(
    private fireAuthService: AngularFireAuth,
    @Inject(FIREBASE_AUTH_PROVIDER) private readonly fireAuthProvider: firebase.auth.AuthProvider,
  ) {}

  login(): Observable<IUser> {
    const responseAuth = from(this.fireAuthService.signInWithPopup(this.fireAuthProvider)).pipe(
// .....

Then in the test the dependency can be easily replaced with a mock:

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      providers: [
        AuthService,
        MockProvider(AngularFireAuth),
        MockProvider(FIREBASE_AUTH_PROVIDER, {}),
      ],
    }).compileComponents();

    service = TestBed.inject(AuthService);
    fireAuthService = TestBed.inject(AngularFireAuth);
  });

  it('should executed login method with success', fakeAsync(() => {
    const googleSignInMethod = spyOn(fireAuthService, 'signInWithPopup');
    googleSignInMethod.and.returnValue(Promise.resolve({
      credential: null,
      user: null,
    }));

    service.login().subscribe((data) => {
      expect(googleSignInMethod).toHaveBeenCalled();
    });

    // const googleSignInMethod = spyOn(fireAuthService, 'signInWithPopup').and.callThrough();
    // service.login().subscribe((data) => {});
    // expect(googleSignInMethod).toHaveBeenCalled();

    // service.login().subscribe((data) => {
    //   let spy = spyOn(fireAuthService, 'signInWithPopup');
    // expect(spy).toHaveBeenCalledWith(provider);
    //   expect(spyOn(fireAuthService, 'signInWithPopup')).toBeTruthy();
    // });
    // spy = spyOn(service, 'isAuthenticated').and.returnValue(false); (3)
    // expect(component.needsLogin()).toBeTruthy();
    // expect(service.isAuthenticated).toHaveBeenCalled(); (4)
  }));

Upvotes: 1

Related Questions