【问题标题】:AngularJS : How to test directive with many dependenciesAngularJS:如何测试具有许多依赖项的指令
【发布时间】:2014-07-25 05:46:34
【问题描述】:

几天以来我一直在尝试测试指令,但找不到任何好的方法。

一点上下文:我们正在使用 Facebook 登录并在引导程序中获取朋友列表。此朋友列表存储在名为 UserConnection 的服务中。该服务有很多方法,但这里有 2 个重要的解决问题的方法

'use strict'

angular.module('AngularApp')
  .service 'UserConnection', ['Configuration', 'localStorageService', 'Logger', '$location', '$q', '$timeout', '$rootScope', (Configuration, localStorageService, Logger, $location, $q, $timeout, $rootScope) ->
    userConnection =
      facebook                    : {}
      friendsList                 : {}

    services = {}

    #
    # Store the Facebook friends list
    #
    services.updateFBFriendsList = (_friendsList_) ->
      lastInteractions = services.getLastUserInteractions()
      friendsList = angular.copy(_friendsList_)
      _.each(friendsList, (friend) ->

        # Add locale to user
        lang = friend.locale.split("_")[0]
        if _.contains(userConnection.translation.availableLanguages, lang)
          friend.lang = lang
        else
          friend.lang = userConnection.translation.defaultLanguage
        delete friend.locale

        # Add firstName
        friend.firstname = friend.first_name
        delete friend.first_name

        # Add recentInteraction
        interaction = _.indexOf(lastInteractions, friend.third_party_id)
        friend.recentInteraction = if interaction >= 0 then interaction else false
      )

      userConnection.friendsList = _.indexBy(friendsList, 'third_party_id')


    #
    # Return every user friends, or one if an attribute is specified
    #
    services.getFBFriendsList = (specificAttribute = null) ->
      if specificAttribute
        return userConnection.friendsList[specificAttribute]
      else
        return userConnection.friendsList

    return services

  ]

让我们回到我要测试的指令。我想测试函数updateLockers。在这个函数中,我调用 UserConnection 服务来检查好友列表中是否存在用户。

'use strict'

angular.module('AngularApp')
  .directive 'lockers', ['Navigation', 'Backend', 'Tools', 'UserConnection',
  (Navigation, Backend, Tools, UserConnection) ->

    replace: true
    restrict: 'A'
    scope: true
    templateUrl: 'views/directives/lockers.html'


    link: ($scope, $elem, $attrs) ->
      directiveId     = "locker"
      directiveReady  = false

      # $scope variables
      $scope.lockers = []
      $scope.active  = false
      $scope.open    = false
      $scope.delete  = false


###################################
# Begin : Handling remote control #
###################################

      # Close the menu
      close = () ->
        $scope.open    = false
        # If the menu was in deleting mode, we remove this
        $scope.delete  = false

      # Open the menu
      open = () ->
        updateLockers()
        $scope.open = true

      # Activate the module
      activate = () ->
        $scope.active = true

      # Desactivate the module
      desactivate = () ->
        $scope.active = false
        close()

      updateNavigation = () ->
        if Navigation.navigationActiveStatus("navigation")
          # If navigation should be activated
          activate()

          # If this module should be open
          if Navigation.navigationOpeningStatus(directiveId)
            open()
          else
            close()

          # If this module should be refreshed
          if Navigation.refreshNavigationStatus(directiveId)
            updateLockers()

        else
          # Navigation must be desactivated
          desactivate()

      $scope.toggle = () ->
        if $scope.open
          Navigation.closeNavigation(directiveId)
        else
          Navigation.openNavigation(directiveId)

      $scope.toggleDelete = () ->
        $scope.delete = !$scope.delete

      # If a component has updated the navigation status, we update
      # the activation status
      $scope.$on 'Navigation:statusUpdated', () ->
        updateNavigation()


#################################
# End : Handling remote control #
#################################


      # Test if there is no more locker in the list
      $scope.isNotEmptyAndActive = () ->
        return $scope.active and $scope.lockers.length > 0


      $scope.buttonClick = (index, toDelete) ->
        if toDelete
          lockerToDelete = $scope.lockers[index]

          Backend.transferStatusToCanceled(lockerToDelete.transfer_id).then(
            (success) ->
              $scope.lockers.splice(index, 1)

              # Close the module is there is no more locker in the list
              Navigation.closeNavigation(directiveId) unless $scope.isNotEmptyAndActive()
          )
        else
          lockerToDownload = $scope.lockers[index]
          UserConnection.redirectToTransferPage(lockerToDownload.sender, lockerToDownload.transfer_id, "internal")



      # Call Backend server to get the last lockers list
      updateLockers = (force = false) ->
        if $scope.active or force
          Backend.getLockerFiles().then(
            (lockers) ->
              $scope.lockers.length = 0
              _.each lockers, (locker) ->
                # We add the transfer only if the sender is still friend with the user
                if UserConnection.getFBFriendsList(locker.sender)
                  # Add the complete sender details
                  locker.senderDetails = UserConnection.getFBFriendsList(locker.sender)
                  locker.humanReadableFileSize = Tools.humanReadableFileSize(locker.file_size)
                  locker.fileType = Tools.getFileType(locker.file_name)
                  locker.fileExt = Tools.getFileExtension(locker.file_name)
                  $scope.lockers.push locker

              if not directiveReady
                # Now the menu is ready to be displayed
                Navigation.directiveReady(directiveId)
                directiveReady = true
          )


      # We load lockers list when user is connected to Backend server
      $scope.$on 'Facebook:userFriendsListLoaded', () ->
        updateLockers(true)

  ]

当我测试函数 updateLockers 时,我们需要在 UserConnection 中拥有 Facebook 好友列表,为了获得这个列表,我需要登录 Facebook,登录我们的后端等等...

我显然想避免这种情况,所以我考虑为 UserConnection 服务创建一个 Mock。但是,这个服务非常庞大(我这里只放了 2 个方法),模拟它会是一项巨大的工作。

我不敢相信没有更好的解决方案。我错过了什么吗?有没有一种简单的方法可以避免嘲笑这项服务?也许通过单元测试策略是完全错误的......谢谢你的建议

【问题讨论】:

  • 显示指令,你这里没有放相关代码。
  • @mpm 我添加了完整的代码,希望对您有所帮助。
  • 如果您不想编写整个 UserConnection 服务的模拟实现,也许您可​​以使用像 in Jasmine 这样的间谍?我认为它通常不如单独的模拟实现好,因为您必须期待对间谍的每次调用,但如果您只需要监视几个调用,它可能比模拟整个服务更容易。
  • @andersschuller :使用 spyOn 和 andCallFake 完美地完成了这个把戏!谢谢你的提示

标签: angularjs unit-testing coffeescript angularjs-directive


【解决方案1】:

在深入挖掘并受到@andersschuller 的启发后,我找到了一个使用 Jasmine 间谍的简单且令人满意的解决方案。

这是我的单元测试。

"use strict"

describe "UT: Directive Locker", ->
  $scope       = undefined
  $element     = undefined
  $element     = undefined

  Navigation     = undefined
  Backend        = undefined
  UserConnection = undefined
  $q             = undefined

  html = '<div lockers></div>'

  compileDirective = () ->
    inject ($compile, $rootScope) ->
      scope = $rootScope
      $element = $compile(html)(scope)
      scope.$digest()
      $scope = $element.scope()
      Navigation._setScope($scope)


  beforeEach ->
    angular.mock.module('AngularApp')
    angular.mock.module('views/directives/lockers.html')

    module ($provide) ->
      $provide.value "Navigation", new NavigationMock
      return

    inject (_Navigation_, _Backend_, _UserConnection_, _$q_) ->
      Navigation = _Navigation_
      Backend = _Backend_
      UserConnection = _UserConnection_
      $q = _$q_

    compileDirective()


  it "should call updateLocker function when Facebook user friends list is loaded", ->
    spyOn(Backend, 'getLockerFiles')
    $scope.$emit('Facebook:userFriendsListLoaded')
    expect(Backend.getLockerFiles).toHaveBeenCalled()

  it "should display lockers inside the list", ->
    spyOn(Backend, 'getLockerFiles').andCallFake ->
      deferred = $q.defer()
      data = [
        {"transfer_id":"538dc0a0fe33db7cc1000001","expiry":3,"created_at":"2014-06-03T12:33:36Z","sender":"tMWyiUflzB5Yg3pC9oEb9JtIi7I","recipient":"vguYIU4KCRQ-Ah0Lz_dq0EKPIi8","file_name":"polnisch P1.pdf","file_size":244965,"chunk_size":1048576,},
        {"transfer_id":"538dc0acfe33dbeea7000001","expiry":3,"created_at":"2014-06-03T12:33:48Z","sender":"tMWyiUflzB5Yg3pC9oEb9JtIi7I","recipient":"vguYIU4KCRQ-Ah0Lz_dq0EKPIi8","file_name":"polnisch P2.pdf","file_size":245193,"chunk_size":1048576,}
      ]
      deferred.resolve(data)
      return deferred.promise

    spyOn(UserConnection, 'getFBFriendsList').andCallFake ->
      return true

    $scope.$emit('Facebook:userFriendsListLoaded')
    $scope.$digest()

    lockers = $element.find(".lockers-list .locker")
    expect(lockers.length).toEqual(2)

通过在函数调用上添加一个 Spy 并添加一个 andCallFake,我可以返回一个带有我需要用于此测试的数据的承诺。然后我就不必模拟完整的服务并制作一个系统来根据执行的测试更改值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-24
    • 1970-01-01
    • 2016-11-24
    • 2011-02-04
    • 2016-03-13
    • 2022-10-20
    相关资源
    最近更新 更多