Kenzo_Gilead
Kenzo_Gilead

Reputation: 2439

Error: Expected spy get to have been called

The following Unit Tests:

    const mockActiveService1 = {
      .......
    }
    const mockNotActiveService1 = {
      .......
    }
    
    describe('Component1 ', () => {
      let component: Component1 ;
      let fixture: ComponentFixture<Component1>;
    
      beforeEach(async () => {
        TestBed.configureTestingModule({
          declarations: [Component1],
          providers: [
            { provide: Service1, useValue: mockActiveService1 }
          ],
          imports: [HttpClientTestingModule, ToastrModule.forRoot()]
        }).compileComponents();
      });
    
      beforeEach(() => {
        fixture = TestBed.createComponent(Component1);
        component = fixture.componentInstance;
        service1 = TestBeb.inject(Service1);
      });
    
    
  it('should load the current dataService2', async () => {
    spyOn(service1, 'get').and.returnValue(of(mockActiveService1));
    
    fixture.detectChanges();

    await component.ngOnInit();

    expect(service1.get).toHaveBeenCalled();
    expect(component.dataService2).toEqual(mockActiveService1);
    expect(component.showCountdown).toBeFalse();
    expect(component.loading).toBeFalse();
  });

  it('should show info message and navigate to default route when there is no active dataService2', fakeAsync (() => {
    const toastrServiceSpy = spyOn(toastrServiceMock, 'info');
    const routerSpy = spyOn(routerMock, 'navigate');
  
    const getSpy = spyOn(service1, 'get').and.callThrough().and.returnValue(of(mockNotActiveService1));
  
    fixture.detectChanges();
    component.ngOnInit();
    tick();
  
    expect(getSpy).toHaveBeenCalled();
    expect(toastrServiceSpy).toHaveBeenCalledWith('no active dataService2', 'Info!');
    expect(routerSpy).toHaveBeenCalledWith(['/default']);
  }));

Both tests are returning the same error:

Error: Expected spy get to have been called.
    at <Jasmine>
    at UserContext.apply (src/app/features/home/home.component.spec.ts:70:37)
    at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30)
    at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43)

I cannot figure the reason out. I tried using different format to build the spy, but nothing looks work. Any clue to help me? The code of the component must call the get method as far as it is:

ngOnInit(): void {
        this.loading = true;
        this.showCountdown = false;
        this.loadCurrentdataService2();
    }
loadCurrentdataService2(): void {
        this.service2.get(0).subscribe(results => {
            if (results) {
                this.dataService2= results;

                if (this.dataService2 && this.dataService2.isActive) {
...............

I have checked the code and the service2.get(0) is being called properly. But if I added the next console.log to the unit test:

it('should load the current dataService2', fakeAsync(() => {
    spyOn(service1, 'get').and.callThrough();

    fixture.detectChanges();

    component.ngOnInit();
    tick();

    console.log('service1.get.calls', (service1.get as any).calls);

    expect(service1.get).toHaveBeenCalled();
    expect(component.dataService2).toEqual(mockActiveService1);
    expect(component.showCountdown).toBeFalse();
    expect(component.loading).toBeFalse();
  }));

I receive:

LOG: 'service1.get.calls', CallTracker{track: function(context) { ... }, any: function() { ... }, count: function() { ... }, argsFor: function(index) { ... }, thisFor: function(index) { ... }, all: function() { ... }, allArgs: function() { ... }, first: function() { ... }, mostRecent: function() { ... }, reset: function() { ... }, saveArgumentsByValue: function() { ... }}

I tried as well:

spyOn(service1, 'get').and.callFake(() => {
return of(mockActiveService1);
});

I can´t figure it out why get method is not being called in the unit test. Please, any help or hint will be really appreciated.

Upvotes: 0

Views: 2941

Answers (2)

Kenzo_Gilead
Kenzo_Gilead

Reputation: 2439

Finally I found the answer. The problem was the service of the component wasn´t the same than the service injected in the test. To solve this, I force to the component´s service to be the same injecting it in the test by the constructor:

it('should load the current dataService2', fakeAsync(inject([Service1], (service1: Service1) => {
      spyOn(service1, 'get').and.callFake(() => {
        return of(mockService1);
      });
      sanitazer.bypassSecurityTrustUrl.and.callFake(input => input);
      sanitazer.bypassSecurityTrustStyle.and.callFake(input => input);
      sanitazer.bypassSecurityTrustHtml.and.callFake(input => input);

      component = new HomeComponent(sanitazer, service1, router, toastrService);

  component.ngOnInit();
  tick(1000);
  fixture.detectChanges();

  expect(service1.get).toHaveBeenCalledWith(0);
  expect(component.campaign).toEqual(mockService1);
  expect(sanitazer.bypassSecurityTrustUrl).toHaveBeenCalledTimes(1);
  expect(sanitazer.bypassSecurityTrustHtml).toHaveBeenCalledTimes(10);
  expect(sanitazer.bypassSecurityTrustStyle).toHaveBeenCalledTimes(5);
  expect(component.showCountdown).toBeTrue();
  expect(component.loading).toBeFalse();
})));

Upvotes: 1

AliF50
AliF50

Reputation: 18859

Ok, try the following out, pay attention to comments with !!.


   describe('HomeComponent', () => {
      let component: HomeComponent;
      let fixture: ComponentFixture<HomeComponent>;
      // !! Declare campaignServiceMock 
      let campaignServiceMock: jasmine.SpyObj<CampaignService>;
      beforeEach(async () => {
        // !! Assign campaignServiceMock to spyObject with the public methods
        // it will have.
        campaignServiceMock =  = jasmine.createSpyObj<CampaignService>('CampaignService', ['get']);
        // !! Add await here since compileComponents is asynchronous.
        await TestBed.configureTestingModule({
          declarations: [HomeComponent],
          providers: [
            // !! We are providing the mock campaignServiceMock for the real
            // CampaignService
            { provide: CampaignService, useValue: campaignServiceMock }
          ],
          imports: [HttpClientTestingModule, ToastrModule.forRoot()]
        }).compileComponents();
      });
    
      beforeEach(() => {
        fixture = TestBed.createComponent(HomeComponent);
        component = fixture.componentInstance;
      });
    
  // !! We can get rid of async here, it should not be needed  
  it('should load the current campaign', /* async */ () => {
    // !! No need to spy anymore since we have a spy object
    // spyOn(campaignService, 'get').and.returnValue(of(mockCampaign));
    // !! Return mock value for the get
    mockCampaignService.get.and.returnValue(of(mockCampaign));
    // !! The first fixture.detectChanges() will call ngOnInit for us.
    // Make sure you don't call fixture.detectChanges() anywhere above this one
    fixture.detectChanges();

    // !! No need to call ngOnInit anymore and also it is not asynchronous
    // so no need for await
    // await component.ngOnInit();
    
    // !! The rest should work
    expect(campaignService.get).toHaveBeenCalled();
    expect(component.campaign).toEqual(mockCampaign);
    expect(component.showCountdown).toBeFalse();
    expect(component.loading).toBeFalse();
  });

Here is how to test components depending on services: https://testing-angular.com/testing-components-depending-on-services/#testing-components-depending-on-services.

Upvotes: 2

Related Questions