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