0

I'm new testing with AngularJs and Jasmine. I'm trying to test a login POST $http service. I'm not pretty sure how to do it, but what I have I'm getting an error and I dont know why.

This is my login.service.js:

(function () {
  'use strict';

  angular
    .module('app')
    .service('loginService', loginService);

  /** @ngInject */
  function loginService($http) {
    var url = 'http://localhost:8080/login';
    var service = {
      login: login
    };
    return service;

    // ////////// //
    function login(user) {
      return $http.post(url, user);
    }
  }
})();

and this is my test:

describe('login component', function () {
  var loginService;
  var httpBackend;
  var user = {
    username: 'ADMIN',
    password: 'ADMIN'
  };

  beforeEach(module('app'));

  beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
    loginService = _loginService_;
    httpBackend = _$httpBackend_;

  }));

  it('loginService should be defined', function () {
    expect(loginService).toBeDefined();
  });

  it('loginService.login should be defined', function () {
    expect(loginService.login).toBeDefined();
  });

  describe('We call the Login Service', function () {
    beforeEach(function () {
      spyOn(loginService, "login").and.callThrough();
    });
    it('we call the service', function () {
      loginService.login(user);
    });

    it('we look if we called the  login service', function () {
      expect(loginService.login).toHaveBeenCalled();
    });

    it('loginService login we send the correct parameters', function () {
      expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
    });
  });
});

I'm getting the next error when it runs:

PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service we look if we called the  login service FAILED
    Expected spy login to have been called.
    .tmp/app/login/login.spec.js:37:50
    loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0) login component We call the Login Service loginService login we send the correct parameters FAILED
    Error: <toHaveBeenCalledWith> : Expected a spy, but got Function.
    Usage: expect(<spyObj>).toHaveBeenCalledWith(...arguments) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 3340)
    .tmp/app/login/login.spec.js:41:54
    loaded@http://localhost:9876/context.js:151:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 6 of 6 (2 FAILED) (0.041 secs / 0.046 secs)

Does anybody know what I'm doing wrong??

THANKS!!!!

Jorge Lara
  • 11
  • 2
  • 6

3 Answers3

3

You're testing it all wrong. Since you're testing the loginService and login is a method defined on that service, you shouldn't mock the method login.

You should only be mocking methods that don't belong to the code(a service in this case) that you're testing. So according to this, you should be mocking the post call that you perform on the $http service.

That can be done like this:

describe('login component', function () {
    var loginService;
    var $httpBackend;
    var user = {
        username: 'ADMIN',
        password: 'ADMIN'
    };

    beforeEach(module('app'));

    beforeEach(angular.mock.inject(function (_$httpBackend_, _loginService_) {
        loginService = _loginService_;
        $httpBackend = _$httpBackend_;

        $httpBackend.whenPOST('http://localhost:8080/login', user).respond(201, 'SUCCESS');
    }));

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    /**
     * Ideally, these should be inside a seperate describe block.
     */
    // it('loginService should be defined', function () {
    //     expect(loginService).toBeDefined();
    // });

    // it('loginService.login should be defined', function () {
    //     expect(loginService.login).toBeDefined();
    // });

    //Like this.
    describe('Initialization', function(){
        it('loginService should be defined', function () {
            expect(loginService).toBeDefined();
        });

        it('loginService.login should be defined', function () {
            expect(loginService.login).toBeDefined();
        });
    });

    describe('function: login', function () {
        /*
         * We should not be spying a method from the service we're trying to test.
         * This is anti-pattern. We should be mocking the post call instead.
         */
        // beforeEach(function () {
        //     spyOn(loginService, "login").and.callThrough();
        // });

        /** 
         * This test is not doing anything. Each it block should have atleast one expect. 
         */
        // it('we call the service', function () {
        //     loginService.login(user);
        // });

        /**
         * This isn't what should be expected here. We should call the method and expect some response.
         * The response will be mocked by us using $httpBackend's expectPOST method. 
         */
        // it('we look if we called the  login service', function () {
        //     expect(loginService.login).toHaveBeenCalled();
        // });

        // it('loginService login we send the correct parameters', function () {
        //     expect(loginService.login).toHaveBeenCalledWith('http://localhost:8080/login', 'POST', user);
        // });
        it('should respond with status 201 and data \'SUCCESS\'', function(){
            var response = loginService.login(user);
            $httpBackend.flush();
            response.success(function(res){
                expect(res).toEqual("SUCCESS");
            });
        });
    });
});

Now you would find that I've commented most of your code. And that's because the practice that has been followed is all wrong.

The practice that should be followed is, you should be using multiple describe blocks for different parts of you code. That's what I've done. Please do go through the comments that I've given. That would help you understand better.

Hope this helps!

SiddAjmera
  • 38,129
  • 5
  • 72
  • 110
  • I just want to say that this is one of the best answers I have ever come across. I never enjoyed testing in AngularJS but 5 minutes ago, I read this article, and 5 minutes later, I finished my unit testing, on my first try! I literally followed this verbatim and it just worked. Bravo my friend, bravo! – User 5842 Jun 19 '18 at 23:56
  • @User5842, glad it helped. :) – SiddAjmera Jun 20 '18 at 07:41
0

Check this out Expected a spy, but got Function

I believe your error is that the method you're after with your spy is actually on the prototype. You'll see in the error trail the Expected a Spy but got a Function. Try spying on the prototype instead:

beforeEach(function () { spyOn(loginService.prototype, "login").and.callThrough(); });

it('we look if we called the login service', function () { expect(loginService.prototype.login).toHaveBeenCalled(); });

Community
  • 1
  • 1
Nathan Beck
  • 1,152
  • 14
  • 23
0

In your case, since you use $http service, you may assert with expectPOST() provided in ngMock, see more in angularjs docs, read the examples in the docs!

Minghao Ni
  • 185
  • 1
  • 9