Как написать модульный и/или e2e-тест для функции, которая вызывает сервисы

Как мне подойти к написанию модульного теста для этой функции? Когда форма отправляется, она отправляет запрос POST для получения токена входа. С ответом токена он затем отправляет еще один запрос, чтобы получить конкретные данные пользователя.

Вот функция:

// Log the user in and save the token, then get the details
onLoginSubmit() {
    this.loggingIn = true;
    // request the token then the details for the user based off the token
    this.userService
        .LoginUser(this.loginForm.value.username, this.loginForm.value.password)
        .pipe(
            tap(res => {
                // console.log('Obtained Token:', res);
                localStorage.setItem('login_token', res.token);
                // this.utilityService.SetLocalStorage('login_token', res.token, 1, 'd');
            }),
            concatMap(() => this.userService.GetTokenDetails()),
        )
        .subscribe(
            res => {
                // console.log('Obtained User Data:', res);
                localStorage.setItem('user', res.user);
                // this.utilityService.SetLocalStorage('user', res.user, 1, 'd');
                this.NavigateToRoute();
            },
            () => {
                this.loggingIn = false;
            },
        );
}

Вот сервисные функции:

// logs in the user
LoginUser(email, password): Observable<any> {
    return this.utilityservice.post('universal/login', { email, password }, this.utilityservice.GetHeaders());
}

// gets details of the current logged in user
GetTokenDetails() {
    return this.utilityservice.get('universal/userAPI', { whoami: '' });
}

Для модульных тестов я предполагаю, что мне нужно создать фиктивную службу, в которой эти функции будут возвращать действительный ответ, например следующий:

// logs in the user
LoginUser(email, password): Observable<any> {
    return { res: { token: 'test' } };
}

Это правильный подход или я совершенно не прав? Кроме того, когда дело доходит до тестирования E2E для моей страницы входа, я, по сути, имитирую пользователя в каждом тесте, пишу код, который имитирует нажатие кнопок формы и ввод значений, а затем проверяет, чтобы убедиться, что я получаю правильные ответы, или я слишком усложняю это?


person anthony    schedule 19.11.2019    source источник
comment
Какой набор тестов вы используете? У большинства есть возможность издеваться над сервисами.   -  person nephiw    schedule 19.11.2019
comment
@nephiw Я не добавлял никаких дополнительных пакетов, поэтому предполагаю Карму и Жасмин.   -  person anthony    schedule 19.11.2019
comment
Это имеет смысл, таковы стандарты для Angular. Я ответил как мог ниже.   -  person nephiw    schedule 19.11.2019


Ответы (2)


Большинство тестовых библиотек, таких как Jasmine или Mocha, будут содержать инструменты имитации, которые помогут вам написать эти тесты; в остальном вы на правильном пути.

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

Я лично использую библиотеки RxJs в своих спецификациях, поэтому я могу создать такой фиктивный класс и предоставить экземпляр:

class MockUserService {
  public loginSubject = new Subject();
  public loginCalls = [];
  // logs in the user
  public LoginUser(email, password): Observable<any> {
    this.loginCalls.push({ email, password });
    return this.loginSubject.asObservable();
  }
}

Так что в другом месте в моих спецификациях я могу сделать что-то вроде этого:

it('calls login on the user service', () => {
  expect(mockUserService.loginCalls.length > 0).toBe(true);
});

it('sets value on local storage', fakeAsync(() => {
  mockUserService.loginSubject.next({ res: { token: 'test-token' }});
  flushMicrotasks();
  expect(mockLocalStorage.setItem).toHaveBeenCalledWith('login_token', 'test-token', 1, 'd');
}));

Обычно Jasmine используется для спецификаций, поэтому вы можете настроить его примерно так (пример спецификации здесь использует структуру jasmine).

loginSubject = new Subject();
const mockUserService = jasmine.createSpyObj('UserService', ['LoginUser']);
mockUserService.LoginUser.and.returnValue(loginSubject.asObservable());

Чтобы это работало должным образом, нужно настроить гораздо больше; Внедрение зависимостей — это здорово, потому что оно позволяет вам писать спецификации, но оно также добавляет некоторый шаблон. Обычно я настраиваю свои тесты вверху следующим образом:

describe('LoginComponent', () => {
  let loginSubject: Subject<any>;
  let mockLocalStorage: jasmine.SpyObj<LocalStorageService>;
  let fixture: ComponentInstance<LoginComponent>;

  beforeEach(() => {
    loginSubject = new Subject();
    const mockUserService = jasmine.createSpyObj('UserService', ['LoginUser']);
    mockUserService.LoginUser.and.returnValue(loginSubject.asObservable());

    mockLocalStorage = jasmine.createSpyObj('LocalStorage', ['setItem']);

    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [
        { provide: UserService, useValue: mockUserService },
        { provide: LocalStorageService, useValue: mockLocalStorage }
      ]
    });

    fixture = TestBed.createComponent(LoginComponent);
  });

  it('calls login on the user service', () => {
    expect(mockUserService.loginCalls.length > 0).toBe(true);
  });

  it('sets value on local storage', fakeAsync(() => {
    loginSubject.next({ res: { token: 'test-token' }});
    flushMicrotasks();
    expect(mockLocalStorage.setItem).toHaveBeenCalledWith('login_token', 'test-token', 1, 'd');
  }));
});

Я делаю массу предположений (например, вы каким-то образом встроили свой шаблон, иначе вам нужно асинхронизировать ваш beforeEach при создании вашего компонента).

В конце концов, вам, вероятно, следует ознакомиться с руководством здесь: https://angular.io/guide/testing

person nephiw    schedule 19.11.2019
comment
Спасибо за четкий ответ. Мне определенно нужно еще многое изучить для этих тестов. - person anthony; 19.11.2019

Вы задали 2 вопроса; относительно тестирования E2E. Страница входа проверяется для всех остальных спецификаций, при условии, что вы должны войти в систему. Моя конфигурация заключается в том, чтобы создать нового пользователя с моей страницей регистрации, выйти из системы, а затем войти в систему с этими учетными данными. После этого я перехожу к другим моим тестам. Вот как я избегаю создания большого количества готовых данных для тестов.

Действительно, то, что важно для e2e, меняется в зависимости от потребностей приложения. Я бы выбрал несколько ваших самых важных страниц и убедился, что они покрыты e2e, потому что спецификации e2e 1.) дороги в создании и 2.) потенциально ненадежны. Тем не менее, страница входа почти всегда является частью «самого важного для приложения», когда она существует.

person nephiw    schedule 19.11.2019