【问题标题】:Angular6 how to test a sequence of http requestsAngular6如何测试一系列http请求
【发布时间】:2018-09-07 09:04:28
【问题描述】:

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

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 不起作用

【问题讨论】:

    标签: angular unit-testing http jasmine


    【解决方案1】:

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

    希望这会有所帮助:-)


    编辑

    由于您想使用内置的东西,我建议您将逻辑切换到可观察的流程。您的问题可能是由于您已承诺所有调用,而不是使用 observable 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 */)
      );
    

    然后在您的测试中,您只需调用 login 方法和 subscribe 到结果(而不是使用 then - 它不再是一个承诺)。

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

    【讨论】:

    • 感谢您迟到评论。但我想知道茉莉花间谍有什么办法吗?因为我认为在 Angular 中使用内置测试框架比安装新框架要好得多。
    猜你喜欢
    • 1970-01-01
    • 2022-10-21
    • 1970-01-01
    • 1970-01-01
    • 2018-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    相关资源
    最近更新 更多