Angular6, как проверить последовательность HTTP-запросов - PullRequest
0 голосов
/ 07 сентября 2018

Я создал сервис для получения информации о пользователях от django rest auth. Поэтому мне нужно 2 отдельных запроса. один для получения токена авторизации и один для получения информации о пользователе.

В службе userService у меня есть метод с именем login, который вызывает 2 других метода. Каждый из них отправляет запрос http на свой URL. Для тестирования поведения 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 Ответ

0 голосов
/ 10 сентября 2018

Вы можете попробовать использовать 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
  });
});

Надеюсь, это немного поможет: -)


EDIT

Поскольку вы хотите использовать встроенный материал, я бы рекомендовал переключить вашу логику на наблюдаемый поток. Ваша проблема может быть вызвана тем, что вы обещали все вызовы, а не используете наблюдаемый 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 - это больше не обещание).

Ваша другая тестовая настройка выглядит просто отлично - проблема в использовании обещаний, на мой взгляд.

...