You need to set up the spy before the method is called. Jasmine spys wrap function to determine when they are called and what they get called with. The method must be wrapped before the spied on method is called in order to capture the information. Try changing your test to match the following:
it('should foobar', () => {
spyOn(component, 'bar');
component.foo();
expect(component.bar).toHaveBeenCalled();
})
Answer from Teddy Sterne on Stack OverflowYou need to set up the spy before the method is called. Jasmine spys wrap function to determine when they are called and what they get called with. The method must be wrapped before the spied on method is called in order to capture the information. Try changing your test to match the following:
it('should foobar', () => {
spyOn(component, 'bar');
component.foo();
expect(component.bar).toHaveBeenCalled();
})
it("should foobar", () => {
const spy = spyOn(component, "bar");
component.foo();
expect(spy).toHaveBeenCalled();
});
[Documentation]: Interaction tests: how to spy on component methods - or is it not permitted?
unit testing - How to spy a service call in Angular2 - Stack Overflow
How to spyOn child components in Angular? - Stack Overflow
unit testing - Angular test - set up a spy on a function of the SUT component before the component is created by TestBed - Stack Overflow
Videos
I take a slightly different approach and use inject itself to get hold of the service instance through DI instead. So your test would look like:
import {
describe,
expect,
it,
tick,
inject,
fakeAsync,
TestComponentBuilder,
ComponentFixture,
addProviders
} from 'angular2/testing';
import { Component, provide } from '@angular/core';
import {UserListComponent} from './user-list.component';
import {UserService} from './user.service';
import {MockUserService} from './user.service.mock';
describe('When loading the UserListComponent', () => {
beforeEach(() => addProviders([
{provide: UserService, useClass: MockUserService}
]));
it('should call the getAllUsers method from the UserService',
inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {
spyOn(mockUserService, 'getAllUsers');
tcb
.createAsync(UserListComponent)
.then((fixture: ComponentFixture) => {
tick();
fixture.detectChanges();
expect(mockUserService.getAllUsers).toHaveBeenCalled();
});
}))
);
it('should show one mocked user',
inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {
mockUserService.setResponse([{
username: 'ryan',
email: '[email protected]'
}]);
tcb
.createAsync(UserListComponent)
.then((fixture: ComponentFixture) => {
tick();
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('div:nth-child(1) .username')).toHaveText('Username: ryan');
expect(compiled.querySelector('div:nth-child(1) .email')).toHaveText('Email: [email protected]');
});
}))
);
});
Edit for Angular 4
The latest docs have a simpler way of getting hold of a service using the component's injector:
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
const mockService = fixture.debugElement.injector.get(MyService);
Edit for Angular 5+
import { Component, Injector, Type } from '@angular/core';
...
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
const mockService = fixture.debugElement.injector.get<MyService>(MyService as Type<MyService>);
The other solutions didn't work for me so I injected the service with injector from debugElement.
import { TestBed,
async } from '@angular/core/testing';
@injectable()
class MyService {
public method () {}
}
let MyMockedService = {
method: () => {}
}
@Component({
template: ''
})
class MyComponent {
constructor(private myService: MyService) {;}
public method () {
this.myService.method();
}
}
describe('Test', () => {
beforeEach(async(() => {
TestBed
.configureTestingModule({
imports: [
CommonModule
],
declarations: [
MyComponent
],
providers: [
{ provide: MyService, useValue: MyMockedService}
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(MyComponent);
myComponent = fixture.componentInstance;
});
}));
it('should spy on service', () => {
let myMockedService = fixture.debugElement.injector.get(MyMockedService);
spyOn(myMockedService, 'method');
myComponent.method();
expect(myMockedService.method);
});
})
I had a stubbed child component where I wanted to confirm that certain functions on it were being called per my component's logic.
I was able to achieve this with the following setup.
Stub component:
@Component({selector: 'child-component', template: ''})
class ChildComponentStub {
someFunction() { /* ... */}
}
Test stage:
it('should spy on a child component function', () => {
const childComponent =
fixture.debugElement.query(By.directive(ChildComponentStub))
.componentInstance;
const functionSpy = spyOn(childComponent, 'someFunction').and.callThrough();
// test logic that causes someFunction to be called
expect(functionSpy).toHaveBeenCalled();
});
(Based on this answer that explains how to get a reference to a child component)
In unit tests everything but tested unit is supposed to be mocked or stubbed. Original <hero-list> should be replaced with a mock:
@Component({
selector: 'hero-list',
template: ''
})
class MockedHeroesListComponent {}
TestBed.configureTestingModule({
declarations: [
HeroesComponent,
MockedHeroesListComponent
]
});
HeroesComponent tests shouldn't test
heroesListComponent.heroesService. All that should be asserted is that <hero-list> was compiled. heroesListComponent.heroesService should be tested in HeroesListComponent test with real HeroesListComponent.
Oke It is fixed! thanks to the answer from @Supamiu
Incase someone will need it in the future:
Move the initialization into ngOnInit, remove fixture.detectChanges(); from beforeEach and execute it in the test. So:
Comp:
constructor(private someService: SomeService) { }
ngOnInit() {
if (this.someService.someData()) {
this.someMethod();
} else {
console.log('keep kalm!');
}
}
test:
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent);
component = fixture.componentInstance;
// fixture.detectChanges(); <<< Remove this
});
it('method should be called', () => {
spyOn(component, 'someMethod');
fixture.detectChanges(); // trigger ngOnInit here
expect(component.someMethod).toHaveBeenCalled();
});
you are setting up the spy too late. By the time you mount the spy on service, it has already been constructed and someMethod has been called. So after defining the component call the spy
it('method should be called', () => {
var spy = spyOn(component, "someMethod").and.callThrough();
expect(component).toBeDefined();
expect(spy);
expect(component.someMethod).toHaveBeenCalled();
});
to mock a service in component's constructor, you need provide the mock before TestBed.createComponent
for example
beforeEach(() => {
mockService = TestBed.get(Service);
spyOn(mockService, 'getSomeValue').and.returnValue({value});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
})
Thank to prototype inheritance, you do it like this :
spyOn(MyService.prototype, 'getX');
const mock = new MyComponent({getX: () => null});
expect(MyService.prototype.getX).toHaveBeenCalled();
You can also do it like this, which is clearer to read :
const serviceMock = new MyService();
spyOn(serviceMock, 'getX');
const mock = new MyComponent(serviceMock);
expect(serviceMock.getX).toHaveBeenCalled();
Be sure to create a mock of your component to trigger the constructor, because if you don't, it will only be done with the TestBed (and your spy won't be in place).
- As I have used providers in the Profile Component, can I inject the Profileervice in the profile.component.spec.ts file as it will create two instance of the same service..??
Answer: You should create a Stub for the service to replace dependency on actual ProfileService using useClass
export class ProfileServiceStub {
getData(id: string) {
return of({some_obj: "value"})
}
}
and in spec.ts
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule],
declarations: [ProfileComponent],
providers: [ {provide: ProfileService , useClass: ProfileServiceStub } ], // <-- useclass here
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
- How to spy on login() and getData() of ProfileService as below statement is not working...?
Answer: Make the service public in component constructor as below:
constructor(public profileService: ProfileService) { }
I would strongly recommend you to read my article on something similar where I have tested the component
1.) I think what you have is fine, putting ProfileService in the provider array in TestBed.configureTestingModule is good and when the component gets created, it knows how to make it. Each time the component gets created, it will be a separate provider but this will be the case whether providing the provider in the component or globally since a test constructs and destructs (beforeEach.... TestBed.configureTestingModule....).
2.) Try `spyOn(profileService, 'login').and.returnValue(Promise.resolve({}));
Side note: The ngOnInit needs an async decorator in order to use await.