TL;DR Mock your service.
Explanation
In your testbed, you provide the actual service :
providers:[ServiceProvidersHTTPService, ...
This means you actually call the real methods of your service.
If you mock your service, like you did there :
{provide: AppConfigService, useClass: MockAppConfigService }
Then you call random functions that you declare in your mock.
Another advantage of mocking, you get rid of the dependencies of your dependencies.
As you said, your service calls another service : if you mock your first service, you don't have to add the dependencies of this service into your testbed.
So, they way to go :
const mock = {
provide: ServiceProvidersHTTPService,
useValue: {
getAllUsers: () => null
}
};
In your testbed now :
providers: [mock, ...
In useValue, you must put all the variables and all the functions of your actual service, and mock them.
In this case, instead of making HTTP calls, your function getAllUsers will simply return null. You can make it return anything you want (the good practice is to return a value that is the same type of the value that should be returned).
Last piece of advice : your unit tests should test only the functions and methods of your actual feature (here being ProjectAnalystComponent). You should not test if your service call the other service here : you should test that in the unit testing of your service.
If you have any questions, feel free to ask !
Answer from user4676340 on Stack OverflowVideos
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);
});
})
However, the test case doesn't work and throws "Error: No provider for Http!".
Because you still have the service in the providers, so Angular is trying to create it still
providers: [
InteractWithServerService,
ChildService],
What you can do instead of creating a mock class is to just do something like
providers: [
{
provide: InteractWithServerService,
useValue: { get: Observable.of(..) }
}
]
Here's you're using useValue which provide any object. That will be the value used when injected. In the case above, it is just some arbitrary object with your mock method.
If you want to spy so that you can provide different values, you could inject the InteractWithServerService, and then do
spyOn(service, 'get').and.returnValue(Observable.of(...))
// do test
Another thing you could do is mock the Http with a dummy object
{ provide: Http, useValue: {} }
Now the InteractWithServerService will work (just adding the class to the providers` like you currently have). And you can just spy on it
let service = TestBed.get(InteractWithServerService);
spyOn(service, 'get').and.returnValue(..)
// do test
Using jasmin 2.6.2: get is a function so you need to add the arrow function notation to the answer above:
providers: [
{
provide: InteractWithServerService,
useValue: { get: () => Observable.of(..) }
}
]
You're calling a fake instead of the function. So the logic inside of the function does not get called.
You have an issue here:
results = configService.getConfig(tstConfigObj).then(function() {
expect(results).toHaveBeenCalled();
expect(results).toHaveBeenCalledWith(tstConfigObj);
});
getConfig takes no parameters, and neither does then. Omitting these errors, results is assigned the string "something" from then. Even if the expect statements fire, you seem to be testing if a string has been called. Try this instead:
results = configService.getConfig().then();
expect(configService.getConfig).toHaveBeenCalled();