How to mock service function in Angular component for unit test

Viewed 96739

I am writing unit test for angular app, I am testing if the service function returns a value.

component.spec.ts

import {TopToolBarService} from '../../top-toolbar/top-toolbar.service';

beforeEach(async(() => {
   TestBed.configureTestingModule ({
   declarations: [ UsersListComponent],
   providers: [TopToolBarService],//tried mocking service here,still test failed
   schemas:[CUSTOM_ELEMENTS_SCHEMA]
 })
  .compileComponents();
}));



beforeEach(() => {
    fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });



  it('should return data from service function', async(() => {
    let mockTopToolBarService: jasmine.SpyObj<TopToolBarService>;
    mockTopToolBarService = jasmine.createSpyObj('TopToolBarService', ['getCustomer']);
    mockTopToolBarService.getCustomer.and.returnValue("king");
    fixture.detectChanges();
    expect(component.bDefine).toBe(true); //fails
  }))

component.ts

bDefine = false;
ngOnInit() {
 let customer = this.topToolBarService.getCustomer();
 if (customer == null) {
   bDefine = false;
 } else {
    bDefine = true;
   }
}

I believe I have mocked the service function in my test, so I expect it must have reached else part where variable is set to 'true'.

TopToolBarService.ts

import { EventEmitter, Injectable, Output } from "@angular/core";

@Injectable()
export class TopToolBarService {
customer = null;

  getCustomer() {
    return this.customer;
  }
}
5 Answers

Try updating providers inside beforeEach(async(() => ...) and moving your mockedService variable on the top of it:

describe('Component TEST', () => {
   ...
   let mockToolBarService;
   ...
      beforeEach(async(() => {
      ...
      mockToolBarService = jasmine.createSpyObj(['getCustomer']);
      mockToolBarService.getCustomer.and.returnValue('king');
      TestBed.configureTestingModule ({
           ...
           providers: [ { provide: TopToolBarService, useValue: mockToolBarService } ]
           ...

Hope it helps!

Change your provider value

beforeEach(() => {
   TestBed.configureTestingModule({
      declarations: [ UsersListComponent],
      providers: [{
         provide: TopToolBarService,
         useValue: jasmine.createSpyObj('TopToolBarService', ['getCustomer'])
      }],
      schemas:[CUSTOM_ELEMENTS_SCHEMA]
     });
     mockTopToolBarService = TestBed.get(TopToolBarService);

     mockTopToolBarService.getCustomer.and.returnValue(of([])); // mock output of function
   })

The answer from @Pedro Bezanilla is good enough. Just here's another way with less code, given the service is a singleton, which I think is most of the case.

The difference is that there's no provider configuration given to configureTestingModule. The spy is set up by spyOn against the service object taken from TestBed instead.

Calling TestBed.inject instead of TestBed.get is because it is deprecated since v9.0.0.

For singleton services, here's a doc in Angular website, and for completion, keep it in mind this GitHub issue.

describe('Component TEST', () => {
  ...
  let mockToolBarService: TopToolBarService;
  ...
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [ UsersListComponent ],
      schemas:[ CUSTOM_ELEMENTS_SCHEMA ]
    });
    mockTopToolBarService = TestBed.inject(TopToolBarService);
    spyOn(mockTopToolBarService, 'getCustomer').and.returnValue(of([]));
  }));
  ...

You might consider usage of ng-mocks to avoid all that boilerplate to configure TestBed.

beforeEach(() => MockBuilder(UsersListComponent)
  .mock(TopToolBarService, {
    // adding custom behavior to the service
    getCustomer: jasmine.createSpy().and.returnValue('king'),
  })
);

it('should return data from service function', () => {
  const fixture = MockRender(UsersListComponent);
  // now right after the render the property should be true
  expect(fixtur.point.componentInstance.bDefine).toBe(true);
}));
Related