在 Angular 中使用多个断言对 HTTP 请求进行单元测试时如何减少代码重复?

How to reduce code duplication when unit testing an HTTP request with multiple assertions in Angular?

我正在使用 Angular 的 HttpClientTestingModuleHttpTestingController 来模拟 HTTP 请求。我的服务 AuthenticationServicelogin 方法做了两件事:

  1. 向服务器发出 POST 请求。如果凭据有效,服务器 returns 一个 User 对象。
  2. 通过在 localStorage 中保存响应来“登录”用户。

因此,我想对我的 login 方法进行两次测试:

  1. 测试用户是否返回
  2. 测试是否设置了localStorage

我可以将两个测试都包含在一个 it 块中,但我不想这样做。我希望每个单元测试有一个断言。

如果我将它们拆分成不同的测试(如下例所示),就会有很多代码重复。有更好的方法吗?

这里是 authentication.service.spec.ts 文件的相关部分。

...

let authenticationService: AuthenticationService;
let httpTestingController: HttpTestingController;
let store = {};

// Create a mock user object for testing purposes
const mockUser: User = {
    name: 'name',
    email: 'email@example.com',
}

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [AuthenticationService],
    });
    authenticationService = TestBed.inject(AuthenticationService);
    httpTestingController = TestBed.inject(HttpTestingController);

    // Create mock localStorage
    spyOn(localStorage, 'getItem').and.callFake(key => {
      return store[key];
    });
    spyOn(localStorage, 'setItem').and.callFake((key, value) => {
      return store[key] = value + '';
    });
    spyOn(localStorage, 'removeItem').and.callFake(key => {
      delete store[key];
    });
});

describe('login', () => {
    it('should return user', () => {
      authenticationService.login('name', 'email@example.com').subscribe(
        data => {
          expect(data).toEqual(mockUser);
        }
      )
      const req = httpTestingController.expectOne('http://example.com/login');
      expect(req.request.method).toEqual('POST');
      req.flush(mockUser);
    });

    it('should set localstorage', () => {
      authenticationService.login('name', 'email@example.com').subscribe(
        data => {
          expect(localStorage.getItem('theUser')).toEqual(JSON.stringify(mockUser));
        }
      )
      const req = httpTestingController.expectOne('http://example.com/login');
      expect(req.request.method).toEqual('POST');
      req.flush(mockUser);
    });

    afterEach(() => {
      httpTestingController.verify();
    });
  });
...

我会同时做两个测试:

it('should return user and set localStorage', () => {
      authenticationService.login('name', 'email@example.com').subscribe(
        data => {
          expect(data).toEqual(mockUser);
          expect(localStorage.getItem('theUser')).toEqual(JSON.stringify(mockUser));
        }
      );
      const req = httpTestingController.expectOne('http://example.com/login');
      expect(req.request.method).toEqual('POST');
      req.flush(mockUser);
    });

但我看你不想那样做。您可以尝试创建一个 re-usable 函数(此处称为 sendLoginResponse)。

describe('login', () => {
    const sendLoginResponse = () => {
      const req = httpTestingController.expectOne('http://example.com/login');
      expect(req.request.method).toEqual('POST');
      req.flush(mockUser);
    };
    it('should return user', () => {
      authenticationService.login('name', 'email@example.com').subscribe(
        data => {
          expect(data).toEqual(mockUser);
        }
      )
      sendLoginResponse();
    });

    it('should set localstorage', () => {
      authenticationService.login('name', 'email@example.com').subscribe(
        data => {
          expect(localStorage.getItem('theUser')).toEqual(JSON.stringify(mockUser));
        }
      )
      sendLoginResponse();
    });

    afterEach(() => {
      httpTestingController.verify();
    });
  });