Angular2 - Unit testing Observable error "Cannot read property 'subscribe' of undefined"

10,999

When you spy a service method and expect to be called, you should add .and.callThrough() on spyOn.

Your code should be like this: spyOn(authServiceStub, 'register').and.callThrough();

And then, expect to haveBeenCalled().

Take a look at: http://blog.danieleghidoli.it/2016/11/06/testing-angular-component-mock-services/

Share:
10,999
A J Qarshi
Author by

A J Qarshi

Working as an Application Developer in a Software Company. Major expertise in Java EE7, C#, and Delphi 2007

Updated on June 09, 2022

Comments

  • A J Qarshi
    A J Qarshi about 2 years

    I have a service function which is returning Observable and I am consuming that service inside one of my components. I have written a unit test for the component using the technique posted here. However, strangely I am getting "cannot read property 'subscribe' of undefined".

    Component under test:

    @Component({
        moduleId: module.id,
        templateUrl: 'signup.component.html'
    })
    export class SignupComponent implements OnInit {
        signupForm: FormGroup;
        submitted = false;    
        registered = false;
    
        constructor(public router: Router, private fb: FormBuilder, public authService: AuthService) {        
    
            this.signupForm = this.fb.group({
                'firstName': ['', Validators.required],
                'lastName': ['', Validators.required],
                'email': ['', Validators.compose([Validators.required])],            
                'password': ['', Validators.compose([Validators.required])],
                'confirmPassword': ['', Validators.required]
            });
        }
    
        ngOnInit() {       
        }
    
        signup(event: any) {
            event.preventDefault();
            this.submitted = true;
    
            if (!this.signupForm.valid) return;
    
            this.authService.register(this.signupForm.value)
                .subscribe(
                (res: Response) => {
                    if (res.ok) {
                        this.registered = true;
                    }
                },
                (error: any) => {
                    this.registered = false;                
                    console.log (error.status + ': ' + error.message);
                });
        }
    }
    

    AbstractMockObservableService

    import { Subscription } from 'rxjs/Subscription';
    
    export abstract class AbstractMockObservableService {
        protected _subscription: Subscription;
        protected _fakeContent: any;
        protected _fakeError: any;
    
        set error(err: any) {
            this._fakeError = err;
        }
    
        set content(data: any) {
            this._fakeContent = data;
        }
    
        get subscription(): Subscription {
            return this._subscription;
        }
    
        subscribe(next: Function, error?: Function, complete?: Function): Subscription {
            this._subscription = new Subscription();
            spyOn(this._subscription, 'unsubscribe');
    
            if (next && this._fakeContent && !this._fakeError) {
                next(this._fakeContent);
            }
            if (error && this._fakeError) {
                error(this._fakeError);
            }
            if (complete) {
                complete();
            }
            return this._subscription;
        }
    }
    

    and finally the unit test

    import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';
    import { By } from '@angular/platform-browser';
    import { DebugElement, Component, NO_ERRORS_SCHEMA } from '@angular/core';
    import { Location } from '@angular/common';
    import { Router } from '@angular/router';
    import { FormBuilder } from "@angular/forms";
    
    import { SignupComponent } from './signup.component';
    import { AuthService } from "../index";
    import { AbstractMockObservableService } from './abstract-mock-observable-service'
    
    let validUser = {
        firstName: "Ahmad", lastName: "Qarshi", email: "[email protected]"
        password: "ahmad#123", confirmPassword: "ahmad#123" 
    }
    
    class RouterStub {
        navigate(url: string) { return url; }
    }
    
    class LocationStub {
        path(url?: string) { return '/security/login'; }
    }
    
    class AuthServiceStub extends AbstractMockObservableService {
        register(model: any) {
            return this;
        }
    }
    
    let authServiceStub: AuthServiceStub;
    let comp: SignupComponent;
    let fixture: ComponentFixture<SignupComponent>;
    let de: DebugElement;
    let el: HTMLElement;
    
    
    function updateForm(firstName: string, lastName: string, email: string
        password: string, confPassword: string) {
        comp.signupForm.controls['firstName'].setValue(firstName);
        comp.signupForm.controls['lastName'].setValue(lastName);
        comp.signupForm.controls['email'].setValue(email);    
        comp.signupForm.controls['password'].setValue(password);
        comp.signupForm.controls['confirmPassword'].setValue(confPassword);    
    }
    
    describe('SignupComponent', () => {
        beforeEach(() => {
            TestBed.configureTestingModule({
                declarations: [SignupComponent],
                schemas: [NO_ERRORS_SCHEMA]
            });
        });
        compileAndCreate();
        tests();
    });
    
    function compileAndCreate() {
        beforeEach(async(() => {
            authServiceStub = new AuthServiceStub();
            TestBed.configureTestingModule({
                providers: [
                    { provide: Router, useClass: RouterStub },
                    { provide: Location, useClass: LocationStub },
                    { provide: AuthService, useValue: authServiceStub },
                    FormBuilder
                ]
            })
                .compileComponents().then(() => {
                    fixture = TestBed.createComponent(SignupComponent);
                    comp = fixture.componentInstance;
                });
        }));
    }
    
    function tests() {
        it('should call register user on submit', fakeAsync(() => {
            comp.submitted = false;
            updateForm(validUser.firstName, validUser.lastName, validUser.email, validUser.username, validUser.password,
                validUser.confirmPassword, validUser.tin, validUser.userType);
    
            spyOn(authServiceStub, 'register');
            authServiceStub.content = { ok: true };
    
            fixture.detectChanges();
            tick();
            fixture.detectChanges();
    
            fixture.debugElement.query(By.css('input[type=submit]')).nativeElement.click();
    
    
            expect(comp.submitted).toBeTruthy('request submitted');
            expect(authServiceStub.register).toHaveBeenCalled();
            expect(comp.registered).toBeTruthy('user registered');
        }));
    }