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

У меня есть следующий сервис:

export class MathService {
  private _total = new BehaviorSubject(0);
  total$ = this._total.asObservable();

  add(num: number) {
    this._total.next(this._total.value() + num);
  }

  subtract(num: number) {
    this._total.next(this._total.value() - num);
  }
}

Как бы вы проверили, что total$ выдает правильные значения в последовательности вызовов функций add и subtract следующим образом:

  service.add(10) // should emit 10;
  service.subtract(3) // should emit 7;
  service.add(20) // should emit 27;
  service.subtract(5) // should emit 22;
  ...

Подойдет ли мраморный тест для чего-то подобного? Если да, то как бы вы это устроили? Мне не удалось найти в Интернете четкий пример того, как проверить, что наблюдаемое в службе выдает правильную последовательность значений с учетом последовательности вызовов функций в этой службе?

Это действительно зависит от того, как вы хотите его протестировать, и если вы знаете, что в тесте (или макетах) вы можете гарантировать, что он всегда будет синхронным. Тогда вы можете подписаться на total$ и собирать все, что он получает. Это также зависит от того, хотите ли вы использовать классические имитационные/шутливые вызовы ожидания или мраморное тестирование RxJS. Вы можете проверить, как Angular тестирует Observables внутри github.com/angular/angular/blob/master/packages/router/test/‌​…

martin 17.03.2022 13:29

Правильно, подписываясь на наблюдаемые и собирая события в массив, а затем проверяя, что элементы в массиве являются теми, которые вы ожидаете увидеть. Я думаю, это работает нормально. Я хотел бы увидеть пример того, как это сделать с мраморными тестами, если это вообще возможно.

snowfrogdev 17.03.2022 13:45
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
2
45
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде всего, я бы попробовал протестировать без мраморных диаграмм, просто чтобы убедиться, что мы понимаем, как будет работать асинхронное выполнение.

it('should test the Observable', () => {
    // create the instance of the service to use in the test
    const mathService = new MathService();
    // define the constant where we hold the notifications
    const result: number[] = [];
    const expected = [0, 0, 0, 1, 0, 2, 0];  // expected notifications

    // this is a sequence of adds
    const add$ = timer(0, 100).pipe(
        take(3),
        tap((i) => {
            console.info('add', i);
            mathService.add(i);
        }),
    );

    // this is a sequence of subtracts, which starts 50 ms after the adds
    const subtract$ = timer(50, 100).pipe(
        take(3),
        tap((i) => {
            console.info('sub', i);
            mathService.subtract(i);
        }),
    );

    // here we subscribe to total$ and we store any notification in the result array
    mathService.total$.subscribe({
        next: (s) => {
            result.push(s);
        },
    });

    // here we merge adds and subtracts and, at completion, we check which are the notifications
    // we have saved in the result array
    merge(add$, subtract$).subscribe({
        complete: () => {
            console.info('===>>>', result, expected);
        },
    });
});

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

let testScheduler: TestScheduler;

beforeEach(() => {
    testScheduler = new TestScheduler(observableMatcher);
});

it.only('should test the Observable', () => {
    testScheduler.run(({ hot, expectObservable }) => {
        const mathService = new MathService();

        const add = hot('        --0-----1---2---------');
        const subtract = hot('   ----0-----1---2-------');
        const expected = '       --0-0---1-0-2-0-------';

        const _add = add.pipe(tap((i) => mathService.add(parseInt(i))));
        const _subtract = subtract.pipe(tap((i) => mathService.subtract(parseInt(i))));
        const result = merge(_add, _subtract).pipe(
            concatMap((val) => {
                console.info('val', val);
                return mathService.total$.pipe(map((v) => v.toString()));
            }),
        );

        expectObservable(result).toBe(expected);
    });
});

Эта реализация следует некоторым примерам тестов, используемых в библиотеке rxjs.

Реализация observableMatcherможно увидеть здесь.

Спасибо за это. Это было очень полезно. Однако я столкнулся с небольшой проблемой при воспроизведении вашей техники для мраморного теста. Все работает нормально, когда поток добавления и вычитания срабатывает на отдельных тиках, но когда я пытаюсь добавить параллельные события, тест начинает давать сбой с неожиданными значениями. Вы случайно не знаете, почему? Может ли это быть из-за того, как работает слияние?

snowfrogdev 21.03.2022 12:00

Можете ли вы поделиться stackblitz с проблемой, с которой вы столкнулись?

Picci 21.03.2022 12:11

Конечно, держи. Вы заметите, что ТРИ немного отличается от той, что была в моем вопросе, но достаточно похожа. stackblitz.com/edit/rxjs-d4qeys?file=index.ts

snowfrogdev 21.03.2022 13:15

У вас своеобразная ситуация, по крайней мере, для шариков. Это версия вашего stackblitz, настроенный для работы с шариками, у которых есть уведомления, которые происходят в одном кадре. Вы можете прочитать мраморный синтаксическая документация для получения более подробной информации, но вкратце суть в том, что у вас есть process1 и process2, которые уведомляют в одном и том же фрейме, и поэтому expected должен указать, что он ожидает более одного уведомления в одном фрейме, используя круглые скобки.

Picci 21.03.2022 17:53

Другие вопросы по теме