I have a service I want to unit test in angular 4 typescript jasmine.
Now, the http
is doing a post
, and it returns an identity, however
With Angular 4.3 came the HttpClient service which replaces Http and provides an easier way to mock HTTP requests. It's well documented on the official page: https://angular.io/guide/http
In order to achieve what you want, the mock you need is a simple function that returns the same as the POST would do normally; another thing is your test should not hit the server for real, so you would need something like this (you might need to add other dependencies):
import { HttpModule } from '@angular/http';
import { TrackerFormService } from './tracker-form.service'
import { Observable } from 'rxjs/Observable'
describe('TrackerFormService', () => {
// Mock the service like this and add all the functions you have in this fashion
let trackerFormService: TrackerFormService,
mockService = {
addSession: jasmine.createSpy('addSession').and.returnValue(Observable.of('your session object mock goes here'))
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [{
provide: TrackerFormService,
useValue: mockService
}]
});
});
// Do this trick to inject the service every time, and just use `service` in your tests
beforeEach(inject([TrackerFormService], (trackerFormService) => {
service = trackerFormService;
}));
describe('addSession', () => {
it('add session ', () => {
let fakeResponse = null;
// Call the service function and subscribe to it to catch the fake response coming from the mock.
service.addSession().subscribe((value) => {
// in here value will be whatever you put as returnValue (remember to keep the observable.of())
fakeResponse = value;
});
// expects as in any test.
expect(fakeResponse).toBeDefined();
expect(fakeResponse).toBe('your session object mock goes here');
});
});
});
Well the way you did setup the test/mock you can fake the return of the post call and check that you got the result you expected. By doing so you will test that the mocked response will be properly converted by your map statement. With your spy you can also check how the post method was called. This will check if the options match what you expect.
But in my opinion that’s a rather complicated solution. I'd prefer to avoid mocks and spies by splitting the method so every method is just doing one thing. Because your addSession method is currently doing three different (yet logically dependent) things:
If you break the method up in three you can easily test method #1 and #3 in separate tests and method #2 would only contain the call to the http library. This allows you to achieve the same test value as above without calling the http library.
Now what about method #2... it's still untested and in my opinion there is no reason to test it at all. Because you did not write that code. Also if you are using angulars http module I am sure they have solid unit tests themselves.
The response of your service should already be covered by an additional integration test, run less frequently checking the service api will still return what you expect.
If you really want that one line green in your code coverage, then you could optionally use a library called nock. Nock will intercept all xhr traffic your app will cause. In your test file you can map xhr requests to mocked responses with the nock object.
var scope = nock('http://myapp.iriscouch.com')
.post('/users', {
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
})
.reply(201, {
ok: true,
id: '123ABC',
rev: '946B7D1C'
});
copied from: https://www.npmjs.com/package/nock
For reference and additional information about testing in general and how much to test i recommend watching "Budgeting Reality" by Justin Searls
Sample test case for http service requests
describe('Forgot Password Controller', function () {
var $controller,
$httpBackend,
$q,
$rootScope,
$state,
controller,
scope,
accountProvider;
beforeEach(module('app'));
beforeEach(inject(function (_$injector_, _$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$httpBackend = _$injector_.get('$httpBackend');
$state = _$injector_.get('$state');
$q = _$injector_.get('$q');
accountProvider = _$injector_.get('accountProvider');
scope = _$rootScope_.$new();
controller = $controller(app.controllers.forgotPassword, {
$state: $state,
accountProvider: accountProvider
});
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
describe('forgot password submission', function () {
it('Can submit a forgot password request successfully', function () {
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(200);
spyOn($state, 'go');
controller.form = { emailAddress: 'aks@gmail.com' };
controller.submit();
expect(controller.submitting).toBe(true);
$httpBackend.flush();
expect(controller.submitting).toBe(false);
expect($state.go).toHaveBeenCalledWith('login', { successMessage: 'An email sent to ' + controller.form.emailAddress + ' contains instructions for resetting your password.' });
});
it('Can handle when a user is not found when submitting a forgot password request', function () {
$httpBackend.expectPOST(app.env.EDGE_SERVICE_PATH + '/events/requestPasswordReset').respond(404);
spyOn($state, 'go');
controller.form = { emailAddress: 'aks@gmail.com' };
controller.submit();
expect(controller.submitting).toBe(true);
$httpBackend.flush();
// We intentionally want to make it appear to the user that the password reset email was sent even when a user
// does not exist, to help hide info about which users exist in the system
expect(controller.submitting).toBe(false);
expect($state.go).toHaveBeenCalledWith('login', { successMessage: 'An email sent to ' + controller.form.emailAddress + ' contains instructions for resetting your password.' });
});
it('Can handle unexpected errors from submitting a forgot password request', function () {
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(500);
controller.submit();
$httpBackend.flush();
expect(controller.errors.unexpectedError).toBe(true);
});
it('Can handle 422 validation errors from submitting a forgot password request', function () {
var responseData = {
fieldErrors: {
username: [{code: 'error'}, {code: 'required', message: 'This is required.'}]
}
};
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(422, responseData);
controller.submit();
$httpBackend.flush();
expect(controller.errors.validationErrors).toBe(true);
expect(controller.errors.fieldErrors).toEqual(responseData.fieldErrors);
});
it('Can handle 503 service unavailable from submitting a forgot password request', function () {
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(503);
controller.submit();
$httpBackend.flush();
expect(controller.errors.serviceUnavailable).toBe(true);
});
});
});