Search code examples
angulartypescriptjasmine

Angular/Jasmine service method was not called


I am trying to do simple test when user click on button it should call method from service. But I am still getting just that method is not called. Component.ts

@Component({
    ...
    providers: [MyService]
})
export class MyComponent implements OnInit, OnDestroy {
    constructor(public myService: myService) { }
}

Component.html

<button id="myId"
        (click)="myService.myMethodX()">
</button>

MyService.ts

    @Injectable()
    
    export class MyService {
    
        constructor() { }
        myMethodX(){
           ...
        }
}

And in jasmine I am testing it like this.

const mySpyObj = jasmine.createSpyObj('MyService', ['myMethodX']);

it('...', () => {
    // Given
    const button = ...

    // When
    button.click();
    fixture.detectChanges();

    // Then
    expect(mySpyObj.myMethodX).toHaveBeenCalled();
});

but it says that its not called what I am doing wrong?


Solution

  • You have to inject your mock into the test scenario, right now you are using real implmenetion. Normally you would do it via providers on the TestBed#configureTestModule but you have your service to be "component" scoped not singleton, thus you must override that as well.

    Here you have working example.Pay attention to the comments and especially overrideComponent call. It has crucial impact as without it real implementation is beeing used instead of our mock.

    // your service
        @Injectable()
        class FooBar {
          foo() {
            return 'bar';
          }
        }
        
    //your component
        @Component({
          template: `
            <button (click)="fooBar.foo()"></button>
          `,
          providers: [FooBar] // FooBar is in scope of component, not singleton, therfore....
        })
        class FooBarComponent {
          constructor(public fooBar: FooBar) {
          }
        }
        
        describe('Foobar', async () => {
          let fooMock: SpyObj<FooBar>;
          beforeEach(async () => {
            fooMock = createSpyObj<FooBar>(['foo']);
            return TestBed.configureTestingModule({
              declarations: [FooBarComponent],
            }).overrideComponent(FooBarComponent, {
              set: { // ..... threfore we are overriding component scope provider - now our mock will be provided instead of real implementation
                providers: [
                  {provide: FooBar, useValue: fooMock} // here you actually start using your mock inside component
                ]
              }
            }).compileComponents();
          });
    
        //passes without a problems
              it('calls foo() on click', () => {
                const fixture = TestBed.createComponent(FooBarComponent);
                fixture.detectChanges();
                fixture.debugElement.query(By.css('button')).nativeElement.click();
                expect(fooMock.foo).toHaveBeenCalled();
              });
            });