3

I have a simple unit test that is done using karma/jasmine on an Angular 6 component. From the pluralsight course and documentation I have gathered it appears I am mocking my service correctly that my component requires, but when the method is called to return data from the mock service I get the error saying property subscribe is undefined.

My "it" function is empty because the test fails once the component is constructed in the beforeEach method. The constructor of the component is what calls the method I am attempting to test. Please see my code below.

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { MainComponent } from './main.component';
import { DataService } from '../services/data.service';
import { of } from 'rxjs';
import { NO_ERRORS_SCHEMA } from "@angular/core";

describe('Should construct MainComponent', () => {
	let mainComponent: MainComponent;
	let EMPLOYEES;
	let fixture: ComponentFixture<MainComponent>;
	let mockDataService;

	beforeEach(() => {
		EMPLOYEES = [
			{
				"PrismEmployeeID": 1,
				"FirstName": "install2",
				"LastName": "account",
				"Initials": "IA ",
				"NickName": "",
				"SSN": "",
				"DateOfBirth": "09/26/2014",
				"HireDate": "09/26/2014",
				"OfficePhone": "9043943239",
				"OfficeFax": "9042246400",
				"ClassificationID": 133,
				"ClassificationDescription": "Claims Support U.S. Insurance",
				"SupervisorID": 32,
				"DepartmentID": 2,
				"DepartmentDescription": "Information Technology",
				"SupervisorName": "Jerry Sutton",
				"CountryID": 1,
				"CountryName": "United States"
			}
		];

		mockDataService = jasmine.createSpyObj(['GetEmployees']);

		TestBed.configureTestingModule({
			declarations: [MainComponent],
			providers: [{ provide: DataService, useValue: mockDataService }],
			schemas: [NO_ERRORS_SCHEMA]
		});

		fixture = TestBed.createComponent(MainComponent);
		fixture.detectChanges();

		mockDataService = jasmine.createSpyObj(['GetEmployees']);
	});

	it('should get an array Employees',
		() => {});
});

MainComponent

import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
import { DataService } from '../services/data.service';
import { TableComponent } from './table/table.component';

@Component({
	selector: 'main',
	templateUrl: './main.component.html',
	styleUrls: ['./main.component.css']
})
export class MainComponent {
	columns: string[] = ['PrismEmployeeID', 'LastName', 'FirstName', 'Initials', 'LastFour', 'BirthDate', 'HireDate', 'OfficePhone', 'OfficeFax', 'ClassificationDescription', 'SupervisorName', 'DepartmentDescription', 'CountryName'];
	loading: boolean = false;
	@Input() tableData: Employee[];

	constructor(private _dataService: DataService) {
		this.loadEmployees();
	}

	loadEmployees() {
		this.loading = true;
		this._dataService.GetEmployees().subscribe((data) => {
			this.loading = false;
			this.tableData = data.json() as Employee[];
		});
	}

	onLoading(loading: boolean) {
		this.loading = loading;
	}

	onReloadData(reloadData: boolean) {
		this.loadEmployees();
	}
}

interface Employee {
	PrismEmployeeID: number;
	FirstName: string;
	LastName: string;
	Initials: string;
	NickName: string;
	SSN: string;
	DateOfBirth: string;
	HireDate: string;
	OfficePhone: string;
	OfficeFax: string;
	ClassificationID: number;
	ClassificationDescription: string;
	SupervisorID: number;
	DepartmentID: number;
	DepartmentDescription: string;
	SupervisorName: string;
	CountryID: number;
	CountryName: string;

	_selected: boolean;
}

<br/>
<h2>Prism Employees</h2>
<div *ngIf="loading" class="d-flex justify-content-center bd-highlight mb-3">
	<div class="p-2 bd-highlight"></div>
	<div class="p-2 bd-highlight">
		<mat-spinner id="overlay"></mat-spinner>
	</div>
	<div class="p-2 bd-highlight"></div>
</div>

<div *ngIf='tableData'>
  <table-component #table (loading)="onLoading($event)" (reloadData)="onReloadData($event)" [data]="tableData" [displayedColumns]="columns"></table-component>
</div>

5
  • thats because you have not configured your spy object to return a value when GetEmployees is invoked. You need to return something Commented Nov 2, 2018 at 12:10
  • How do you configure a spy object to return a value? Commented Nov 2, 2018 at 12:38
  • I tried spyOn(mockDataService, 'GetEmployees').and.returnValue(EMPLOYEES); and now get error GetEmployees has already been spied upon Commented Nov 2, 2018 at 12:42
  • I leaved an answer Commented Nov 2, 2018 at 13:29
  • thanks trying it out now Commented Nov 2, 2018 at 14:33

2 Answers 2

11

The issue is that you are currently providing a stub of your service with no configured return for the GetEmployees method. Meaning that once the component invokes the previous function and subscribe to its (undefined) return, it will trigger an exception.

To solve this, you need to fake the return of that method. Based on this answer you can try this as follows:

import {of} from 'rxjs';
...
mockDataService = jasmine.createSpyObj(DataService.Name, {'GetEmployees': of(EMPLOYEES)});
...

UPDATE:

In order for this to work though, you will have to refactor your DataService.GetEmployees method to have the following signature:

GetEmployees(): Observable<Employee[]>;

The current implementation of DataService.GetEmployees is a leaky abstraction, as it returns the raw Response object from the old Http API, forcing the consumer (in this case the component) to know details about the underlying implementation (this detail being the use of data.json() as Employee[])

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

2 Comments

I changed the service with your recommended changes and used the new HttpClient api and the error was resolved. Thanks!
Thanks a lot after some hours of googling found correct workout!
0
spyOn(mockYourService, 'getSomeData').and.returnValue({ subscribe: () => {} })

You could... Return an actual observable in the spy

spyOn(mockYourService, 'getSomeData').and.returnValue(Observable.of(someData)) Maybe this will be preferred over the loop subscribe method, because if the call on the service is changing something in the component, this is something you probably will want to test

Comments

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.