0

I am trying to test if my navigate function is called deleting a vehicle. But it seems to never call. I think this might have something to do with the fact that it's an async function but even when I wait for the full subscribe to finish it doesn't work.

.ts:

deleteVehicle(): void {
    if (confirm('Bent u zeker dat u deze wagen wilt verwijderen?')) {
      this.apiService.deleteVehicle(this.selectedVehicle.id).subscribe(() => this.navigateToListVehicleComponent());
    }
  }

spec.ts:

beforeEach(() => {
    fixture = TestBed.createComponent(VehicleDetailsComponent);
    component = fixture.componentInstance;

    apiService = TestBed.get(ApiService);
    fixture.detectChanges();
});
describe('#deleteVehicle', () => {
    it('should navigate to list vehicle component', fakeAsync(() => {
      spyOn(window, 'confirm').and.returnValue(true);
      spy = spyOn(apiService, 'deleteVehicle').withArgs(component.selectedVehicle.id).and.callThrough();
      let navigateSpy = spyOn(component,'navigateToListVehicleComponent').and.callThrough();

      component.deleteVehicle();
      tick();
      expect(spy).toHaveBeenCalled();
      expect(navigateSpy).toHaveBeenCalled();
    }));
  });

service.ts:

private deleteFromAPI(url): any {
    return this.http
    .delete(this.BASE_API_URL + url)
    .pipe(catchError(this.handleError));
}
deleteVehicle(id: number): any {
    return this.deleteFromAPI('vehicle/' + id);
}

and in my testbed I import HttpClientTestingModule.

error: Expected spy navigateToListVehicleComponent to have been called.

Anyone know what I'm doing wrong?

JorenVDZ
  • 5
  • 5
  • Can you show the setups for your tests? Did you mock `apiService.deleteVehicle` method? – Lin Du Dec 16 '20 at 06:14
  • @slideshowp2 I changed a bit of the code I hope its easier to understand what I'm doing now. I basically am mocking my apiService with the HttpClientTestingModule and then I spy on the apiService.deleteVehicle method. – JorenVDZ Dec 16 '20 at 10:51
  • You are using `callThrough()` which will delegate to the actual implementation. It might cause the issue. Can you post the actual implementation for `apiService.deleteVehicle()` method? – Lin Du Dec 16 '20 at 12:00
  • I just edited the post with the implementation of the apiService.deleteVehicle() hope this helps – JorenVDZ Dec 16 '20 at 12:36

1 Answers1

0

My test strategy is to stub the apiService.deleteVehicle method so that it will have no side effect(send real HTTP request via network).

unit test solution using angular v11+:

example.component.ts:

import { Component } from '@angular/core';
import { ApiService } from './api.service';

@Component({})
export class ExampleComponent {
  selectedVehicle = {
    id: 1,
  };
  constructor(private apiService: ApiService) {}
  navigateToListVehicleComponent() {
    console.log('navigateToListVehicleComponent real implementation');
  }

  deleteVehicle(): void {
    if (confirm('Bent u zeker dat u deze wagen wilt verwijderen?')) {
      this.apiService
        .deleteVehicle(this.selectedVehicle.id)
        .subscribe(() => this.navigateToListVehicleComponent());
    }
  }
}

api.service.ts:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ApiService {
  BASE_API_URL = 'http://localhost';
  constructor(private http: HttpClient) {}

  private deleteFromAPI(url): any {
    return this.http
      .delete(this.BASE_API_URL + url)
      .pipe(catchError(this.handleError));
  }
  private handleError(err) {
    return of(err);
  }
  deleteVehicle(id: number): any {
    return this.deleteFromAPI('vehicle/' + id);
  }
}

example.component.spec.ts:

import { HttpClient, HttpClientModule } from '@angular/common/http';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
import { ApiService } from './api.service';
import { ExampleComponent } from './example.component';

fdescribe('#deleteVehicle', () => {
  let apiService: ApiService;
  let fixture: ComponentFixture<ExampleComponent>;
  let component: ExampleComponent;

  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [ExampleComponent],
        imports: [HttpClientModule],
        providers: [ApiService, HttpClient],
      })
        .compileComponents()
        .then(() => {
          fixture = TestBed.createComponent(ExampleComponent);
          apiService = TestBed.get(ApiService);
          component = fixture.componentInstance;
          fixture.detectChanges();
        });
    })
  );
  it('should navigate to list vehicle component', () => {
    const confirmSpy = spyOn(window, 'confirm').and.returnValue(true);
    const deleteVehicleSpy = spyOn(apiService, 'deleteVehicle')
      .withArgs(component.selectedVehicle.id)
      .and.returnValue(of('deleteVehicle fake implementation'));

    let navigateSpy = spyOn(
      component,
      'navigateToListVehicleComponent'
    ).and.stub();

    component.deleteVehicle();

    expect(confirmSpy).toHaveBeenCalledOnceWith(
      'Bent u zeker dat u deze wagen wilt verwijderen?'
    );
    expect(deleteVehicleSpy).toHaveBeenCalledOnceWith(1);
    expect(navigateSpy).toHaveBeenCalled();
  });
});

unit test result:

enter image description here

Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Thank you so much this fixed it! – JorenVDZ Dec 22 '20 at 13:18
  • is it important to pass correct with Args arguments in spyOn ? I am facing issue where my method is never invoked, but problem is that arguments are coming from another component and are dynamic in nature – kuldeep Jan 31 '23 at 16:55