RxJS log value pipe

RxJS log value pipe

Sometimes, instead of using debugger keyword, you'll just want to display the values in the console. When using RxJS you'll probably use quick&dirty tap(value => console.log(value)). There's nothing wrong with it, but it can be done a little nicer and manageable. I'll create a custom RxJS operator to achieve this.

Setup

Requirements: Node, npm, npx

Install dependencies

npm i jest@29 @types/jest@29 ts-jest rxjs typescript

Init ts-jest

npx ts-jest config:init

logValue operator

This operator uses the native tap operator to call the console.log(). Simple as that.

import { MonoTypeOperatorFunction } from 'rxjs';
import { tap } from 'rxjs/operators';

export function logValue<T>(): MonoTypeOperatorFunction<T> {
  return tap((value: T): void => {
    console.log(value);
  });
}

So why use it anyway? There are a couple of advantages:

  • easily find usages of this operator instead of searching the whole project for console.log string

  • easily replace console.log with any other logging mechanism

  • easily add some logic to enable/disable logging (i.e. use environment variable)

  • less code and ease of use

Test it

The test performs the following operations:

  • spies on console.log method

  • emits single and multiple values in the stream

  • tests the number of calls and the arguments of the spy

import { from, of } from 'rxjs';
import { logValue } from './log-value.operator';

describe('Test logValue operator', () => {
  let spy: jest.SpyInstance;

  beforeEach(() => {
    spy = jest.spyOn(console, 'log');
  });

  afterEach(() => {
    spy.mockClear();
  });

  describe('Test single logValue call', () => {
    beforeEach(() => {
      of('the string').pipe(logValue()).subscribe();
    });

    it('should call console.log once', () => {
      expect(console.log).toHaveBeenCalledTimes(1);
      expect(console.log).toHaveBeenNthCalledWith(1, 'the string');
    });
  });

  describe('Test multiple logValue calls', () => {
    beforeEach(() => {
      const data = [1, 2, 3, 4, 5];
      from(data).pipe(logValue()).subscribe();
    });

    it('should call console.log 5 times', () => {
      expect(console.log).toHaveBeenCalledTimes(5);
      expect(console.log).toHaveBeenNthCalledWith(1, 1);
      expect(console.log).toHaveBeenNthCalledWith(2, 2);
      expect(console.log).toHaveBeenNthCalledWith(3, 3);
      expect(console.log).toHaveBeenNthCalledWith(4, 4);
      expect(console.log).toHaveBeenNthCalledWith(5, 5);
    });
  });
});

Run npx jest and check the results. By default, you'll get some console.log outputs on the screen, but the final result should be similar to:

 PASS  src/log-value.operator.spec.ts
  Test logValue operator
    Test single logValue call
      ✓ should call console.log once
    Test multiple logValue calls
      ✓ should call console.log 5 times

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total