Stephan K.
Stephan K.

Reputation: 15702

How to properly test Angular's $http success and error promises with Jasmine

I have such controller:

$scope.signIn = (user) ->
      if user.email != '' and user.password != ''
        UserService.signIn(user)

I have such service:

 signIn: (user) ->
        $http.post '/user/signin', user

        .success (data) ->
          AuthService.set 'email', data.user.local.email

        .success () ->
          $state.go 'auth.profile'

        .error () ->
          throw Error(err)

I have such test so far:

describe 'AuthController Unit Tests', ->

  beforeEach module('kreditorenApp')

  AuthController  = undefined
  scope           = undefined
  AuthService     = undefined
  UserService     = undefined
  $httpBackend    = undefined
  $controller     = undefined
  $location       = undefined
  user            = undefined
  HTTPInterceptor = undefined
  $alert          = undefined

  resUser =
    "status": "OK"
    "error": "Erfolgreich Authentifiztiert"
    "type": "success"
    "token": "TOKEN STRING"
    "user":
      "__v": 0
      "_id": "5512a0dfcbc8ea974647f493"
      "local":
        "password": "HASH STRING"
        "email": "dieter"


  beforeEach inject(($rootScope, _$controller_, _$httpBackend_, _UserService_, _AuthService_, _$location_, _$alert_, _HTTPInterceptor_) ->

    scope         = $rootScope.$new()
    $controller   = _$controller_
    $httpBackend  = _$httpBackend_
    $location     = _$location_
    UserService   = _UserService_
    AuthService   = _AuthService_
    HTTPInterceptor = _HTTPInterceptor_

    # Stubs
    #
    httpFake = () ->
      bla:
        "bla"

    userFake = () ->
      signIn:
        "SUCCESS"

    # Spies
    #
    spyOn(HTTPInterceptor, 'response').and.callFake(httpFake)
    # spyOn(UserService, 'signIn').and.callFake(userFake)

    $httpBackend.expectPOST('/user/signin', user).respond(200, {user":"local":"email":"emperorRudolf"})
    $httpBackend.expectGET('app/controller/auth/auth.html').respond(200)
    $httpBackend.expectGET('app/controller/auth/partials/profile.html').respond(200)

    AuthController = $controller('AuthController', $scope:scope)
  )

  afterEach ->
    $httpBackend.verifyNoOutstandingExpectation()
    $httpBackend.verifyNoOutstandingRequest()

  it 'should signIn user & ui-route to auth.profile & invoke HTTPInterceptor', ->

    # Tests
    #
    scope.signIn ({email:'mail', password:'pass'})
    $httpBackend.flush()

    #expect(HTTPInterceptor.response).toHaveBeenCalled
    #expect many more things later

The test is failing with

 TypeError: 'undefined' is not an object (evaluating 'data.user')

I want to mock out as few as possible.

Edit: The data object is always undefined. $http's rather unique .success method is not accessible or known to my tests. How do I test this properly?

Upvotes: 0

Views: 1014

Answers (2)

Stephan K.
Stephan K.

Reputation: 15702

This is how it is done, for an error. Grab a reference to the promise that we reject, provide an error message, and catch the error. I am still working on the .success promise and will update as soon as I got it running. I think I will switch to Sinon because this seems much to complicated for testing my controller/service duo.

describe 'AuthController Unit Tests', ->

  beforeEach module('myApp')

  UserService = undefined
  httpMock = undefined
  $q = undefined
  $scope = undefined

  beforeEach module('myApp', ($provide) ->
    httpMock = jasmine.createSpyObj('$http', ['post'])
    $provide.value('$http', httpMock)
    return
  )

  beforeEach inject((_$controller_, $rootScope, _$q_, _UserService_) ->
    $scope = $rootScope.$new()
    $controller = _$controller_
    $q = _$q_
    UserService = _UserService_

    AuthController = $controller('AuthController',
      $scope: $scope
    )
  )

  it 'should catch an error', ->

    errorMsg = 'Unauthorized'
    defer = $q.defer()
    defer.reject(errorMsg)
    httpMock.post.and.returnValue(defer.promise)

    UserService.signIn('1').catch (error) ->
      expect(error).toEqual(errorMsg)

    $scope.$digest();
    return

Please note the return after $provide.value('$http', httpMock) is mandatory otherwise CoffeeScript will come and haunt you. Props to Simon Bailey's Angular Testing Cookbook for providing help in this matter.

Upvotes: 1

JB Nizet
JB Nizet

Reputation: 691715

You code posts to /user/signin, and expects to get back a response body containing a user:

 $http.post '/user/signin', user

    .success (data) ->
        AuthService.set 'email', data.user.local.email

But when you're mocking the backend, you make it return the following:

$httpBackend.expectPOST('/user/signin', user).respond(200, {"asd":"asd"})

So, obviously, trying to access user.local.email on the object {"asd":"asd"} won't work.

Upvotes: 0

Related Questions