6

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');
    }));
}
1
  • @A J Qarshi did you ever figure this out Commented Apr 14, 2017 at 17:39

2 Answers 2

6

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/

Sign up to request clarification or add additional context in comments.

Comments

0

Can you import this in your app.module or in your unit test

 import { Observable } from 'rxjs/Rx';
 import 'rxjs/Rx';

2 Comments

I don't think I need to include this in my unit test. However, I tried and it didn't work
Same here, I have the same problem. it should be in the setup I think. I have mocked Subscription but still no chance!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.