Home > OS >  Angular Unit Test - Cannot read properties of undefined (reading 'subscribe')
Angular Unit Test - Cannot read properties of undefined (reading 'subscribe')

Time:01-02

I am using Angular / Jasmine for unit tests.
Currently I am testing a component with an Input field "Add" button.
"Add" should call a add() function with a service behind it.

I want to test if the service method was called.

I have two test for this.
One is working, (Add value to Input field & click the "Add" Button)
One is not working. (calling the add() method directly behind the "Add" Button)\

The error ERROR: TypeError: Cannot read properties of undefined (reading 'subscribe') indicates that the service is undefined. But I don't understand why the service is in one testcase defined and in the other not?

Here is a Stackblitz with Jasmine-Test : https://stackblitz.com/edit/angular-ivy-dnzv96?file=src/app/app.component.spec.ts

HTML

<div>
  <label>Hero name:
    <input #heroName />
  </label>
  
  <button (click)="add(heroName.value)">
    add
  </button>
</div>

The add() method

 add(name: string): void {
    this.heroService.addHero({ name })
      .subscribe(hero => {
        this.heroes.push(hero);
      });
  }

The two Test

  describe('should call HeroService.addHero()',() => {

    **// WORKING TEST**
    it('by using input dialog and click "Add" button', () => {
      const input = el.query(By.css('input')).nativeElement;
      input.value = 'NewHero';
      const button = el.query(By.css('button'));
      button.triggerEventHandler('click', null)
      fixture.detectChanges;

      expect(mockHeroService.addHero).toHaveBeenCalledWith({ name: 'NewHero' } );
    })

    **// FAILING TEST - ERROR: TypeError: Cannot read properties of undefined (reading 'subscribe')**
    it('by calling method add()', (() => {
      component.add('newHero');

      expect(mockHeroService.addHero).toHaveBeenCalledWith({ name: 'NewHero'} );
    }))
  })

CodePudding user response:

You are not returning the data from mockHeroService spy and hence getting an error. In the spec file you an make modifications as:

        const hero = {
          id: 1,
          name: 'NewHero',
          strength: 80,
        }

        // within describe
        it('by using input dialog and push add button', () => {
          const stubValue = 'NewHero';
          mockHeroService.addHero.and.returnValue(of(hero));
          // existing logic
          expect(mockHeroService.addHero).toHaveBeenCalledWith({ name: 'NewHero' } );
        })
        
        it('by calling method add()', (() => {
          const stubValue = 'newHero';
          mockHeroService.addHero.and.returnValue(of(hero));
          component.add('newHero');
          expect(mockHeroService.addHero).toHaveBeenCalledWith({ name: 'newHero'} );
        }))

Also in ts file, you can add an if statement to guard against null/undefined as:

    this.heroService.addHero({ name })
       .subscribe(hero => {
         if (this.heroes) {
            this.heroes.push(hero);
         }
    });

CodePudding user response:

The error ERROR: TypeError: Cannot read properties of undefined (reading 'subscribe') indicates that the service is undefined. But I don't understand why the service is in one testcase defined and in the other not?

I think this is not the case: the error tells that the service is defined, the addMethod is called, but the value returned is undefined instead of an Observable:

this.heroService.addHero({ name }) // this is fine
      .subscribe(...); // this is not 

So, I think the problem is in how you setup your mockHeroService, I'm guessing that you're not returning anything in the mocked addHero method.

CodePudding user response:

Thanks to Chiesa and Siddhant.

Chiesa explained the error. Siddhants suggested a solution. Nothing to add.

Here is the working example.

https://stackblitz.com/edit/angular-ivy-p1ccau?file=src/app/app.component.spec.ts

  • Related