我试图测试指令几天,我找不到任何好方法。
一些背景信息:我们正在使用Facebook登录并在bootstrap获取好友列表。此朋友列表存储在名为UserConnection的服务中。这项服务有很多方法,但这里有两个重要问题
'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。然而,这项服务是巨大的(我在这里只提出了两种方法),并且嘲笑它将是一项巨大的工作。
我无法相信没有更好的解决方案。我错过了什么吗?是否有一个简单的替代方法来避免嘲笑这项服务?也许单元测试策略是完全错误的...感谢您的建议
答案 0 :(得分: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,我可以返回一个包含此测试所需数据的promise。然后,我不必模拟完整的服务,并根据执行的测试使系统更改值。