angularjasminekarma-jasmine

How to test a callback of a mocked function in a service with jasmine?


A method of a service that I am using in my component has a callback as the second argument. When This callback is executed it returns a value which is assigned to a variable in the component.

I want to mock the method in my service for unit testing and assert in the unit test that the service method was able to update the variable of my component.

export class AppComponent {
      title = 'sample-app';
      sampleValue: string = '';

      constructor(private dataService:DataService)
      {}
  
      getSampleValue()
      {
        this.dataService.fetchData('Sample Value',(response) => {
          this.sampleValue = response;
        });
      }
    }



    export class DataService {
      constructor() {}

      fetchData(inputData:string,callback:(outputData:string)=>void) {
        const output:string = inputData + ' from service';
        callback(output);
      }
    }



    describe('AppComponent', () => {
      let component: AppComponent;
      let fixture: ComponentFixture<AppComponent>;
      let mockDataService:jasmine.SpyObj<DataService>;

      beforeEach(() => {
    
        mockDataService = jasmine.createSpyObj('DataService', ['fetchData']);
    
        TestBed.configureTestingModule({
          imports: [RouterTestingModule],
          declarations: [AppComponent],
          providers: [{
             provide: DataService,
             useValue: mockDataService
          }]
        });

        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
    
      });


      it(`should set sample value as 'Sample Value from service'`, () => {
        const fixture = TestBed.createComponent(AppComponent);
        const app = fixture.componentInstance;
        mockDataService.fetchData('Sample Value', (response) => {
          app.sampleValue = response;
        })
        expect(app.sampleValue).toEqual('Sample Value from service');
      });

     });


Solution

  • You can use callFake method of jasmine, to define the method of the fetchData.

    You are also missing compileComponents method execution on TestBed.configureTestingModule.

    I am assuming, you have an API call on the service, so you can use fakeAsync and flush to wait for the API call to complete before running the test.

    import { TestBed, ComponentFixture, waitForAsync, fakeAsync, flush } from '@angular/core/testing';
    import { AppComponent, DataService } from './app.component';
    
    describe('AppComponent', () => {
      let component: AppComponent;
      let fixture: ComponentFixture<AppComponent>;
      let mockDataService: jasmine.SpyObj<DataService>;
    
      beforeEach(() => {
        mockDataService = jasmine.createSpyObj('DataService', ['fetchData']);
        TestBed.configureTestingModule({
          imports: [],
          declarations: [AppComponent],
          providers: [
            {
              provide: DataService,
              useValue: mockDataService,
            },
          ],
        }).compileComponents();
      });
    
      beforeEach(waitForAsync(() => {
    
        fixture = TestBed.createComponent(AppComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      }));
    
      it(`should set sample value as 'Sample Value from service'`, fakeAsync(() => {
        const app = fixture.componentInstance;
        mockDataService.fetchData.and.callFake((inputData: string, callback: (outputData: string) => void) {
          const output: string = inputData + ' from service';
          callback(output);
        });
        app.getSampleValue();
        flush();
        expect(app.sampleValue).toEqual('Sample Value from service');
      }));
    });
    

    Stackblitz Demo