I'm trying to write a unit test to determine that a property on my angular 2 component is populated after a service call that returns a promise.
My component contains a method:
getListItems() {
this.employeeService.loadEmployees().then(res => {
this._employees = res['Employees'];
this.ref.markForCheck();
});
};
That calls a method on a service:
@Injectable()
export class EmployeeService {
constructor(
public http: Http
) { }
public loadEmployees(): Promise<any> {
return this.http.get('employees.json')
.map((res: Response) => res.json())
.toPromise();
}
}
Which grabs the content from a local json file (in lieu of creating a remote endpoint). This works well enough, but I'd like to replace the service method when testing with one that makes a call to a local json-server.
As far as I can tell I've got the json-server spinning up/down correctly while the Karma tests run - I can use Postman to successfully perform a GET request on it while the tests are running.
I'd therefore like to replace my service with the mock:
class MockEmployeeService {
headers: Headers;
constructor(private http: Http) {
this.headers = new Headers({ 'Content-Type': 'application/json' });
}
public loadEmployees() {
return this.http.get('http://localhost:3004/getEmployees',
{headers: this.headers, body: '' })
.map((res: Response) => res.json());
}
}
and I've set up the unit test as follows:
describe('ListComponent', () => {
let fixture;
let component;
let employeeService;
beforeEach( async(() => {
TestBed.configureTestingModule({
imports: [
HttpModule
],
declarations: [
ListComponent
],
providers: [
{provide: EmployeeService, useClass: MockEmployeeService}
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
employeeService = fixture.debugElement.injector
.get(EmployeeService);
});
it('should retrieve test values from json-server (async)',
async(() => {
fixture.detectChanges();
component.getListItems();
fixture.whenStable.then(() => {
expect(component._employees).toBeDefined();
});
}));
})
So (I think) I'm calling the method on the component which should call the service, which should be replaced by the MockEmployeeService
. I get the following Karma error:
× should retrieve test values from json-server (async)
PhantomJS 2.1.1 (Windows 7 0.0.0)
Failed: Can't resolve all parameters for MockEmployeeService: (?).
I'm pretty much at the boundaries of my knowledge at this point and I'm having trouble finding online resources for newer, TestBed-using tests. Is anyone able to spot anything in here that looks off?
If you are mocking the service, you should not be using Http
. Just return your own promise, without interacting with Http
.
class MockEmployeeService {
public loadEmployees() {
return Promise.resolve({some:'object'});
}
}
With the Http
service, you would be making an XHR call, which you don't want to do during the test. For mocks, we want to make it simple as possible, as we want to have it affect the component in test as least as possible.
Another problem with Http
during testing is that it is dependent on the platform browser, which is not available in a test environment. That doesn't mean we can't use it. We just need to use a helper class that Angular provides for testing.
We covered mocking the service for testing components, but if you also wanted to test your service, You need to create the Http
provider yourself using some Angular test helper class MockBackend
.
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions
]
});
With the MockBackend
, we can subscribe to connections and mock responses. For a complete example of that, please see this post
See Also:
Promise
, if you are not that familiar with creating your own promises.