Home  >  Angular

Angular Test Spy on Method

By Arvind Rai, 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.

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 Jasmine createSpyObj creates an object with multiple spies as its members.
createSpyObj(baseName, methodNames, propertyNamesopt) 
baseName : We can pass class name as base name.
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']); 
2. To return a different value from spy method, we need to configure as following.
const testEmp = { id: '101', name: 'Mohan' };
getEmpByIdSpy = empServiceSpy.getEmpById.and.returnValue(of(testEmp)); 
Now on calling 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. The fakeAsync 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 code
employee.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);
  } 
} 
app.component.ts
@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('');
			})
		);		
	}
} 
app.component.html
<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('');
  }));

}); 
Find the print screen of the test output.
Angular Test Spy on Method

Reference

Component testing scenarios

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI











©2022 concretepage.com | Privacy Policy | Contact Us