Services often depend on other services that Angular inject into the constructor.
Create and inject these dependencies by hand.
These standard testing techniques are great for unit testing services in isolation.
However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.
Angular testing utilities, like TestBedmake it straightforward to investigate how injected services behave.
Only use this way if testing real simple Services.
Prefer injecting with spies as they are usually a better way to mock services.
describe("", () => {
let masterService: MasterService;
it('#getValue should return real value from the real service', () => {
masterService = new MasterService(new ValueService());
expect(masterService.getValue()).toBe('real value');
});
it('#getValue should return faked value from a fakeService', () => {
masterService = new MasterService(new FakeValueService());
expect(masterService.getValue()).toBe('faked service value');
});
it('#getValue should return faked value from a fake object', () => {
const fake = {getValue: () => 'fake value'};
masterService = new MasterService(fake as ValueService);
expect(masterService.getValue()).toBe('fake value');
});
});
Injecting with spies
describe("", () => {
let masterService: MasterService;
it('#getValue should return stubbed value from a spy', () => {
// Create `getValue` spy on an object (in this case method) representing the ValueService
const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
// Set the value to return when the `getValue` spy is called.
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
masterService = new MasterService(valueServiceSpy);
expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
});
});
Services with TestBed
Angular DI handle the discovery and creating of dependant services.
As a service consumer, you don't worry about the order of constructor arguments or how they are created. For this you use TestBed testing utility to provide and create services.
Simple Service test
You set the providers property with an array of the services that you'll test or mock.
Then inject it inside a test by calling TestBed.inject() with the service class as the argument.
// This is how the `ValueService` testing file should be
describe("", () => {
let service: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({ providers: [ValueService] });
});
it("", () => {
service = TestBed.inject(ValueService);
expect(service.getValue()).toBe("");
});
});
Injecting other services as dependencies
When testing a service with a dependency, provide the mock in the providers array.
describe("", () => {
let masterService: MasterService;
let valueServiceSpy: jasmine.SpyObj<ValueService>;
beforeEach(() => {
const spy = jasmine.createSpyObj("ValueService", ["getValue"]);
TestBed.configureTestingModule({
// Provide both the service-to-test and it's spy dependency
providers: [MasterService, { provide: ValueService, useValue: spy }],
});
masterService = TestBed.inject(MasterService);
valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;
});
it('#getValue should return stubbed value from a spy', () => {
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
});
});
describe('ApiService', () => {
let service: ApiService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
// In the docs it imports different ones
providers: [HttpClientTestingModule],
});
service = TestBed.inject(ApiService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
// Finally, we can assert that no other requests were made.
httpMock.verify();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should fetch data and return list of fruits', () => {
const expectedFruits = ['grapes', 'pineapple'];
service.getFruits().subscribe((response) => expect(response).toEqual(expectedFruits));
// At this point, the request is pending, and we can assert it was made via the `HttpTestingController`
const req = httpMock.expectOne('url/fruits');
// We can assert various properties of the request if desired.
expect(req.request.method).toBe('GET');
// Flushing the request causes it to complete, delivering the result.
req.flush(expectedFruits);
});
});
Testing error handling
To test handling of backend errors (when the server returns a non-successful status code), flush requests with an error response that emulates what your backend would return when a request fails.