Angular6如何测试HTTP请求序列

时间:2018-09-07 09:04:28

标签: angular unit-testing http jasmine

我已经创建了一项服务,用于从django rest auth获取用户信息。所以我需要2个单独的请求。一种用于获取身份验证令牌,另一种用于获取用户信息。

userService服务中,我有一个名为login的方法,该方法调用了另外两个方法。他们每个人都向不同的网址发送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.

真正的问题是,由于HttpClientTestingModule不起作用,我如何才能测试http请求的顺序

1 个答案:

答案 0 :(得分:4)

您可以尝试使用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
  });
});

希望这会有所帮助:-)


编辑

由于您要使用内置的内容,因此建议您将逻辑切换为可观察的流程。您的问题可能是由于您将所有呼叫都告知了您,而不是使用了可观察的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 */)
  );

然后在您的测试中,您只需调用登录方法并向结果中subscribe(而不是使用then-不再是诺言)。

您的其他测试设置看起来还不错-我认为问题在于使用了Promise。