Angular6 如何测试一系列的 HTTP 请求

7
我创建了一个服务,用于从django rest auth获取用户信息。因此需要两个不同的请求,一个用于获取认证令牌,另一个用于获取用户信息。
在userService服务中,我有一个名为login的方法,该方法调用了另外两个方法。它们各自向不同URL发送HTTP请求。 为了测试login的行为,我需要模拟这两个方法的请求。 第一个方法返回一个Promise,其中包括身份验证密钥,第二个方法返回一个Promise,其中包括用户对象。 以下是服务类中的代码:
public getAuthToken(identifier: string, password: string) {
  const requestBody = is_valid_email(identifier) ? {email: identifier, password: password} :
                                                 {username: identifier, password: password};
  let savedToken = getFromStorage('auth');
  if (savedToken) {
    try {
      savedToken = JSON.parse(savedToken);
    } catch (e) {
      savedToken = null;
    }
  }
  return new Promise((resolve, reject) => {
    if (savedToken) {
      resolve(savedToken);
    } else {
      this.http.post<string>(APIUrlSolver.login, requestBody).subscribe(data => {
        const dataObj = JSON.parse(data);
        UserService._auth_token = dataObj['key'];
        resolve(dataObj['key']);
      }, error1 => {
        // Rejection code. removed for better reading
      });
    }
  });
}

public getUserProfile(): Promise<UserModel> {
  return new Promise<UserModel>((resolve, reject) => {
    this.http.get(APIUrlSolver.user).subscribe((data: string) => {
      const jsonData = JSON.parse(data);
      const userObj = new UserModel(jsonData.username, jsonData.email, jsonData.first_name, jsonData.last_name, jsonData.phone,
        jsonData.birth_date);
      UserService._user = userObj;
      resolve(userObj);
    }, error1 => {
      // Rejection code. removed for better reading
    });
  });
}

public login(identifier: string, password: string) {
  return new Promise((resolve, reject) => {
    this.getAuthToken(identifier, password).then(key => {
      this.getUserProfile().then(user => {
        // Will resolve user object
      }).catch(error => {
        UserService._auth_token = undefined;
        reject(error);
      });
    }).catch(reason => {
      UserService._auth_token = undefined;
      reject(reason);
    });
  });
}

我尝试使用以下代码测试该方法:

      describe('userService', () => {
  let userService: UserService;
  let httpClient: HttpTestingController;
  const mockedUser = new UserModel();
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });
    userService = TestBed.get(UserService);
    httpClient = TestBed.get(HttpTestingController);
  });

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

    it('#login', () => {
    const authResponse = {key: '74f0d5ffb992f5f49533d25c686f36414e64482c'};
    const response = {username: 'daaaaaaab', email: 'test@test.ir', first_name: 'test', last_name: 'test', phone: '09123657894',
      birth_date: '2018-07-31'};
    const expectedUser = new UserModel(response.username, response.email, response.first_name, response.last_name, response.phone,
      response.birth_date);

    userService.login('identifier', 'password').then(user => {
      expect(user).toEqual(expectedUser);
      expect(userService.user).toEqual(expectedUser);
      });

    const req = httpClient.expectOne(APIUrlSolver.login);   // This response works correct
    expect(req.request.method).toBe('POST');
    req.flush(JSON.stringify(authResponse));

    const userReq = httpClient.expectOne(APIUrlSolver.user);    // I get error here
    expect(req.request.method).toBe('GET');
    userReq.flush(JSON.stringify(response));
    });

     });

但是这段代码在userReq上总是会失败,因为expectOne会抛出异常:
 Error: Expected one matching request for criteria "Match URL: /user/user/", found none.

真正的问题是如何测试这个http请求序列,因为HttpClientTestingModule无法工作。

1个回答

5
你可以尝试使用TypeMoq来模拟你的HTTP客户端。以下代码应该会帮助你朝着正确的方向前进。不过,它尚未经过测试。
describe('user service', () => {

  const tokenData = { key: 'asdasd' };
  const userResponse = { name: 'user', /* ... and so on */ };

  let mockHttpClient: TypeMoq.IMock<HttpClient>;
  let mockUserService: TypeMoq.IMock<UserService>;

  beforeAll(async(() => {

    mockHttpClient = TypeMoq.Mock.ofType<HttpClient>();
    mockHttpClient.setup(x => 
      x.get(TypeMoq.It.isValue(APIUrlSolver.login)).returns(() => of(tokenData))
    );
    mockHttpClient.setup(x => 
      x.get(TypeMoq.It.isValue(APIUrlSolver.user)).returns(() => of(userResponse))
    );

    mockUserService = TypeMoq.Mock.ofType<UserService>();

    TestBed
      .configureTestingModule({
        declarations: [],
        providers: [
          { provide: HttpClient, useValue: mockHttpService.object},
          { provide: UserService, useValue: mockUserService.object},
        ]
      })
      .compileComponents();
  }));

  let userService: UserService;

  beforeEach(async(() => {
    userService = TestBed.get(UserService);
  });

  it('login flow', async () => {
     const user = await userService.login('identifier', 'password');
     mockUserService.verify(x => x.getToken, TypeMoq.Times.once());
     mockUserService.verify(x => x.getUserProfile, TypeMoq.Times.once());
     mockHttpService.verify(x => x.get, TypeMoq.Times.exactly(2));
     // ... any other assertions
  });
});

希望这能有点帮助 :-)

编辑

由于您想使用内置的内容,我建议您将逻辑切换为可观察流。您的问题可能是由于所有调用都被 Promise 化而不是使用可观察的 API 导致的。

这是我会写的方式 - 您可以尝试它,看看是否有帮助:-)

public getAuthToken(identifier: string, password: string): Observable<string> {

  let savedToken = getSavedToken();
  if(savedToken) return of(savedToken);

  return this.http.post<string>(APIUrlSolver.login, getRequestBody(identifier, password)).pipe(
    // Instead of this mapping you can use `this.http.post<YourDataStructure>`
    // and the JSON deserialization will be done for you
    map(data => JSON.parse(data)),
    map(data => UserService._auth_token = data['key']),
    // You can either handle the error here or let it fall through and handle it later
    catchError(err => /* error handling code */)
  );

public getUserProfile(): Observable<UserModel> {
  return this.http.get(APIUrlSolver.user).pipe(
    // Again, if your response JSON already looks like the `UserModel`,
    // you can simply write `this.http.get<UserModel>` ...
    map(data => {
      let d = JSON.parse(data);
      return UserService._user = new UserModel(d.username, d.email, d.first_name, d.last_name, d.phone, d.birth_date);
    }),
    catchError(err => /* as stated before... */)
  );

public login(identifier: string, password: string) {
  return this.getAuthToken(identifier, password).pipe(
    switchMap(key => this.getUserProfile()),
    catchError(err => /* Your login-wide error handler */)
  );

在你的测试中,只需调用登录方法并订阅结果(而不是使用then - 它不再是一个promise)。

你的其他测试设置看起来很好 - 我认为问题在于使用promises。


谢谢您,很抱歉回复晚了。但我想知道是否有使用jasmine spy的方法?因为我认为在Angular中使用内置的测试框架比安装新框架要好得多。 - Mr Alihoseiny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接