Angular Test Spy on Method
May 04, 2022
On this page we will learn to spy object of service methods. Spy objects are created using Jasmine.createSpyObj
method. Service method can be synchronous or asynchronous. To test async method, we need to use fakeAsync
function.
Here on this page we will provide complete example to create spy of a service class.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 13.1.0
2. Node.js 12.20.0
3. Jasmine 3.10
4. Karma 6.3
Jasmine.createSpyObj
The JasminecreateSpyObj
creates an object with multiple spies as its members.
createSpyObj(baseName, methodNames, propertyNamesopt)
methodNames : Array of method names to create spies.
propertyNames : Array of property names to create spies.
Find the sample code.
1. Here we have created the spy object for
EmployeeService
with getEmpById
method.
const empServiceSpy = jasmine.createSpyObj('EmployeeService', ['getEmpById']);
const testEmp = { id: '101', name: 'Mohan' }; getEmpByIdSpy = empServiceSpy.getEmpById.and.returnValue(of(testEmp));
getEmpById
in our test, testEmp
will be returned.
3. The spy object needs to be configured in testing module.
TestBed.configureTestingModule({ declarations: [AppComponent], providers: [{ provide: EmployeeService, useValue: empServiceSpy }] })
Use fakeAsync() for Async Methods
1. ThefakeAsync
wraps a function to be executed in fakeAsync
zone.
2. To use
fakeAsync()
functionality, we must import zone.js/testing
in our test setup file. But if we are using Angular CLI, zone-testing
is already imported in src/test.ts
.
3. The
fakeAsync()
function is used to test async service methods such as methods using HTTP or setTimeout()
.
4. The code which returns
Observable
by using RxJS of
is synchronous code. To test this method, using fakeAsync()
is not necessary.
5. The syntax to use
fakeAsync()
is given below.
it('description', fakeAsync(() => { --- }));
Complete Example
A. Application codeemployee.service.ts
@Injectable({ providedIn: 'root' }) export class EmployeeService { empUrl = "/api/employees"; constructor(private http: HttpClient) { } getEmpById(empId: string): Observable<any> { return this.http.get<Employee>(this.empUrl + "/" + empId); } }
@Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit { $emp = of({} as any); errorMessage = ''; constructor(private empService: EmployeeService) { } ngOnInit() { const id = '110'; this.$emp = this.empService.getEmpById(id).pipe( catchError((err: any) => { setTimeout(() => this.errorMessage = err.message || err.toString()); return of(''); }) ); } }
<p class="employee">{{($emp | async).name}}</p> <p class="error" *ngIf="errorMessage">{{ errorMessage }}</p>
B. Test code
app.component.spec.ts
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { of, throwError } from 'rxjs'; import { EmployeeService } from './employee.service'; describe('#AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture<AppComponent>; let getEmpByIdSpy: any; const testEmp = { id: '101', name: 'Mohan' }; beforeEach(async () => { const empServiceSpy = jasmine.createSpyObj('EmployeeService', ['getEmpById']); getEmpByIdSpy = empServiceSpy.getEmpById.and.returnValue(of(testEmp)); await TestBed.configureTestingModule({ declarations: [AppComponent], providers: [{ provide: EmployeeService, useValue: empServiceSpy }] }).compileComponents(); fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; }); it('should show employee name after component initialized', fakeAsync(() => { const empName = fixture.nativeElement.querySelector('.employee'); fixture.detectChanges(); expect(empName.textContent).toBe(testEmp.name); expect(getEmpByIdSpy.calls.any()) .withContext('getEmpById called') .toBe(true); })); it('should display error when EmployeeService fails', fakeAsync(() => { getEmpByIdSpy.and.returnValue(throwError(() => new Error('EmployeeService test failure'))); fixture.detectChanges(); // Initializes the component tick(); // flush the component's setTimeout() fixture.detectChanges(); // Updates the component const error = fixture.nativeElement.querySelector('.error'); const empName = fixture.nativeElement.querySelector('.employee'); expect(error.textContent) .withContext('should display error') .toContain('EmployeeService test failure'); expect(empName.textContent) .withContext('should show no employee') .toBe(''); })); });
