如何正确模拟 Angular 服务? Karma Jasmine 测试:预期间谍 service.getShipPhotos 已被调用一次。被调用0次

How to mock Angular service properly? Karma Jasmine test: Expected spy service.getShipPhotos to have been called once. It was called 0 times

我正在使用 Angular 12 和 Karma-Jasmine 4 并正在测试以验证函数 retrieveShip 正在调用函数 getSingleShip,但测试结果显示它根本没有被调用。 我怀疑正在调用真正的服务而不是模拟服务。 如何正确模拟服务?

以下依赖于 mockShipsService.getSingleShip 的类似测试失败(预期间谍 ShipsService.getSingleShip 已被调用一次。它被调用了 0 次。)。

spec.ts:

 beforeEach(async () => {
    const spyShipServ = jasmine.createSpyObj('ShipsService', ['getSingleShip']);
    mockActivatedRoute = {
      snapshot: {
        paramMap: {
          get: () => { return "610a80fd485a6ad03b43b539" }
        }
      }
    }

    await TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
        RouterTestingModule.withRoutes(routes),
      ],
      declarations: [
        DetailedShipComponent
      ],
      providers: [
        { provide: ShipsService, useValue: spyShipServ },
        { provide: ActivatedRoute, useValue: mockActivatedRoute },
      ],
    }).compileComponents();
    mockShipsService = TestBed.inject(ShipsService) as jasmine.SpyObj<ShipsService>
    router = TestBed.inject(Router);
  });

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


  describe('retrieveShip', () => {
    it('should call getSingleShip once', () => {
      getSingleShipResponse = {
        _id: "610a80fd485a6ad03b43b539",
        name: "Sabrina",
        type: "cruise"
      };
      mockShipsService.getSingleShip.and.returnValue(of(getSingleShipResponse));

      component.retrieveShip("610a80fd485a6ad03b43b539");

      expect(mockShipsService.getSingleShip).toHaveBeenCalledTimes(1);
    })
  })

以上是根据 Angular 文档完成的:Testing services

component.ts:

  retrieveShip(shipId: string): void {
    this.shipsService.getSingleShip(shipId).subscribe({
      next: response => {
        this.ship = response;
      },
      error: err => this.errorMessage = err
    });
  }

service.ts:

@Injectable({
  providedIn: 'root'
})
export class ShipsService {
  readonly ROOT_URL: string = environment.ROOT_URL;

  constructor(
    private http: HttpClient,
  ) {
  }

  getSingleShip(shipId: string): Observable<Ship> {
    return this.http.get<Ship>(`${this.ROOT_URL}/ships/${shipId}`);
  }
}

如有任何提示,我们将不胜感激!

通常您会提供一个模拟服务(如果像这样的服务执行 HTTP 调用,或者不与使用相同服务的其他单元测试混在一起,则不会产生真正的后果)并提供该模拟。然后你监视那个模拟,创建一个对象或只是间谍,并给它你期望的 return 值。我会说,你是 95% 正确。

还记得 fakeAsync 和 tick() 异步调用。如果你 return of(element) (又名可观察或承诺)它不会同步计算调用。

例如:

const spyShipServ: Partial<ShipsService> = { 
   public getSingleShip(shipId: string): Ship { return null; }

describe('retrieveShipComponent', () => {
  it('should call getSingleShip once', fakeAsync(() => {

    //you're not doing anything with the return, so and.returnValue isn't necessary here
    spyOn(spyShipServ, 'getSingleShip'); 

    component.retrieveShip("610a80fd485a6ad03b43b539");
    tick();
    expect(spyShipServ.getSingleShip).toHaveBeenCalledTimes(1);
  ))}
}

我得出以下解决方案:注入真实服务并使用 callFake()。

spec.ts:

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [
      HttpClientTestingModule,
    ],
    declarations: [
      DetailedShipComponent
    ],
  })
    .compileComponents();
});

beforeEach(() => {
  fixture = TestBed.createComponent(DetailedShipComponent);
  component = fixture.componentInstance;
  service = fixture.debugElement.injector.get(ShipsService);
});

describe('retrieveShip', () => {
 it('should call getSingleShip once', waitForAsync(() => {
   spyOn(service, "getSingleShip").and.callFake(() => {
     return of(singleShipResponse);
   })

   component.retrieveShip("610a80fd485a6ad03b43b539");

   expect(service.getSingleShip).toHaveBeenCalledTimes(1);
 }))
})

测试现在通过了!

这是让我产生想法的视频: https://www.youtube.com/watch?v=l4oYN3TvKM4