【问题标题】:How to overwrite the implementation of a method when testing an Angular service with Jest?使用 Jest 测试 Angular 服务时如何覆盖方法的实现?
【发布时间】:2020-07-04 05:35:46
【问题描述】:

编辑:

事实证明,问题不是我在此处提供的代码部分,而是我忽略的部分。我不会删除问题,以防有人发现示例和答案有用。


我需要使用 Jest 测试 Angular 服务。 GameService 有一个 start 方法,它调用 manage 方法。为了测试start,我只需要监视manage 以检查它是否被调用,但要避免它的实际执行。

jest.spyOn() 方法本身不会覆盖方法的原始实现。需要自己提供该实现。文档提出了两种方法:

  • jest.spyOn(gameService, 'manage').mockImplementation(() => {});
  • gameService.manage = jest.fn(() => {});

但是,尽管这两种方法都允许我监视 manage 的调用,但它们并没有避免执行实际的 manage 方法。

这似乎是a recurrent problem,但我还没有找到适合我的解决方案。我只是想知道是否有人在 Angular 中遇到过类似的问题并且可以找到解决方案。

我的服务代码及其规范如下(我将省略不相关的测试用例):

game.service.ts

import { Injectable } from '@angular/core';
import { OneDeviceModule } from '../one-device.module';
import { PlayersService } from './players.service';
import { Player } from '../../models/player';

@Injectable({
  providedIn: OneDeviceModule
})
export class GameService {
  public players: Player[];

  constructor(private playersService: PlayersService) { }

  public start(): void {
    const players = this.playersService.getPlayersList();
    if (players.length < 2) {
      throw new Error('There must be at least two players to start the game');
    }
    this.players = players;
    this.manage();
  }

  public manage(): void {
    while(this.goesOn()) {

    }
  }

  public goesOn(): boolean {
    return true;
  }
}

game.service.spec.ts

import { TestBed } from '@angular/core/testing';
import { mocked } from 'ts-jest/utils';
import { GameService } from './game.service';
import { PlayersService } from './players.service';
import { Player } from '../../models/player';

jest.mock('./players.service');
const mockedPlayersService = mocked(PlayersService, true);

const players = [
  new Player('John'),
  new Player('Anna'),
  new Player('Julia')
];

describe('GameService', () => {
  let gameService: GameService;
  let playersService: PlayersService;

  beforeEach(() => {
    TestBed.configureTestingModule({ providers: [GameService, PlayersService]});
    gameService = TestBed.inject(GameService);
    playersService = mockedPlayersService.mock.instances[0];
    (playersService.getPlayersList as jest.Mock).mockReturnValue(players);
  });

  afterEach(() => {
    mockedPlayersService.mockClear();
    (playersService.getPlayersList as jest.Mock).mockClear();
  });
  
  describe('constructor', () => {
    ...
  });

  describe('start', () => {    
    function onePlayerSetup(playersService: PlayersService): void {
      (playersService.getPlayersList as jest.Mock).mockClear();
      (playersService.getPlayersList as jest.Mock).mockReturnValue([new Player('John')]);
    }
    const mockManage: jest.Mock<void, never[]> = jest.fn().mockImplementation(() => {});

    ...

    it('should not call manage method if there are less than 2 players', () => {
      onePlayerSetup(playersService);
      gameService.manage = mockManage;
      try { gameService.start(); }
      catch { }
      finally { expect(mockManage).toHaveBeenCalledTimes(0); }
    });

    it('should call manage method if everything is ok', () => {
      gameService.manage = mockManage;
      gameService.start();
      expect(mockManage).toHaveBeenCalledTimes(1);
    });
  });
});

【问题讨论】:

    标签: angular unit-testing jestjs spy


    【解决方案1】:

    虽然我从未使用过 ts-jest/utils/mocked,但我会使用 Angular 的依赖注入来确保使用模拟服务而不是原始服务,因为它是完成这项工作的完美工具:

      let gameService: GameService;
      let playersService: PlayersService;
    
      beforeEach(() => {
        playersService = // prepare your mock
        TestBed.configureTestingModule({ providers: [
          GameService,
          { provide: PlayersService, useValue: playersService } // inject your mock
        ]});
        gameService = TestBed.inject(GameService);
        // ...
      });
    

    您甚至可以提供更简单的 playerService 版本:

      let gameService: GameService;
      let playersService = { getPlayersList: () => [] };
      
      beforeEach(() => {
        TestBed.configureTestingModule({ providers: [
          GameService,
          { provide: PlayersService, useValue: playersService } // inject your mock
        ]});
        gameService = TestBed.inject(GameService);
      });
      
      it('should not call manage method if there are less than 2 players', () => {
        playersService.getPlayersList = () => [new Player('John')];
        expect(gameService.start()).toThrow();
      });
    

    【讨论】:

    • 这是模拟另一个依赖项的好选择,但这不是我的测试有什么问题,因为依赖项被正确模拟了。我的问题是如何在我正在测试的依赖项中模拟其他方法。
    猜你喜欢
    • 2016-07-09
    • 2020-12-04
    • 2020-03-12
    • 2022-06-10
    • 1970-01-01
    • 2018-11-15
    • 2022-01-21
    • 1970-01-01
    • 2014-02-09
    相关资源
    最近更新 更多