0

Background

I was trying to test my authService.getUserProfile() function that looks like below

  private authUserSubject$ = new BehaviorSubject<IUser | null>(null);
  authUser$ = this.authUserSubject$.asObservable();

  getUserProfile = () => this.httpClient.get<IUser>('/user').pipe(
    tap(this.authUserSubject$.next)
  );

I needed to test that when getUserProfile() is called, then authUser$ is an Observable that has the user object

To achieve this I used the below code

  describe('function getUserProfile()' , () =>{
    it('should return update authUser$ property', done => {
      spyOn(httpClient, 'get').and.returnValue(of({name: 'John Doe'}))
      service.getUserProfile().subscribe({
        next: (res) => {
          expect(res as any).toEqual({name: 'John Doe'});
          done();
        }
      })
    });
  })

The above test fails

Uncaught TypeError: Cannot read property 'length' of undefined thrown

In the console log for my test I have

zone-evergreen.js:178 Uncaught TypeError: Cannot read property 'length' of undefined
    at AuthService.next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/Subject.js:36)
    at AuthService.next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/BehaviorSubject.js:30)
    at TapSubscriber._next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/operators/tap.js:40)
    at TapSubscriber.next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/Subscriber.js:49)
    at Observable._subscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/util/subscribeToArray.js:3)
    at Observable._trySubscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/Observable.js:42)
    at Observable.subscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/Observable.js:28)
    at DoOperator.call (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/operators/tap.js:16)
    at Observable.subscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/Observable.js:23)
    at UserContext.<anonymous> (:9876/_karma_webpack_/webpack:/src/app/login/services/auth.service.spec.ts:38)

The above should have worked, In fact in the test coverage the code is covered in the testing. To try solve the problem I tried binding this in my function like

getUserProfile = () => this.httpClient.get<IUser>('/user').pipe(
   tap(this.authUserSubject$.next.bind(this)) // <-- This is line 38
 );

Now I was so sure this will work but to my surprise same error. Finally I decided to try the expanded code i.e

getUserProfile = () => this.httpClient.get<IUser>('/user').pipe(
   tap(res => this.authUserSubject$.next(res))
 );

The above actually worked...

My question

What I am trying to understand is

  1. Why would

    tap(this.authUserSubject$.next.bind(this)) <-- Throws error

    tap(res => this.authUserSubject$.next(res)) <-- No error thrown

  2. Where is the length property coming from?

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • 2
    Correct use of `bind()` would be like this `tap(this.authUserSubject$.next.bind(this.authUserSubject$))` – martin Dec 17 '20 at 09:54
  • 3
    I believe you could do just `tap(this.authUserSubject$)`, but it will also notify subject about `erorr`s and `complete` event – Andrei Dec 17 '20 at 09:58
  • @martin that has worked, though I seem not to get the logic behind how it works, but it works – Owen Kelvin Dec 17 '20 at 10:01
  • Instead of passing `this.authUserSubject$.next` to `tap`, pass the whole Subject (which is an `Observer` as well), like this: `tap(this.authUserSubject$)`. – Mladen Dec 17 '20 at 10:12
  • @Andrei thats very interesting! It has also worked – Owen Kelvin Dec 17 '20 at 10:23
  • Some more discussion about this here: https://stackoverflow.com/questions/65094060/subject-call-to-next-causing-a-strange-error – Mrk Sef Dec 17 '20 at 15:11

0 Answers0