Frontend Unit Testing
Why ?
Because you're not Chuck Norris. Chuck Norris' code tests itself, and it always passes, with 0ms execution time.
Pros
Increase your code modularity
Never forget dependencies
Ensure you logic is working
Faster than e2e tests
First step to TDD
Cons
Too much frameworks !
How ?
Test runner
BDD framework
Example n°1
Controller
angular.module 'pepiniere-dashboard-sirf.admin'
.controller 'AdminSignsCtrl', ($log, $scope, Sign, signs) ->
$scope.signs = signs
removed = []
refresh = (list) ->
list.forEach (item, i) ->
item.position = i
$scope.add = (index) ->
$scope.signs.splice index, 0, new Sign()
refresh $scope.signs
...
$scope.doUpdate = (form) ->
for item in removed
item.$delete()
for item in $scope.signs
item.$save()
return
describe 'AdminSignsCtrl', ->
$controller = null
$rootScope = null
$state = null
Sign = null
beforeEach module 'pepiniere-dashboard-sirf.admin'
beforeEach inject (_$controller_, _$rootScope_, _$state_) ->
$controller = _$controller_
$rootScope = _$rootScope_
$state = _$state_
beforeEach inject (_Sign_) ->
Sign = _Sign_
describe 'router', ->
state = 'admin.signs'
it 'should redirect to url', ->
expect($state.href state).toEqual '#/admin/signs'
it 'should resolve data', ->
Sign.find = jasmine.createSpy('find').and.returnValue $promise: []
$state.go state
$rootScope.$digest()
expect(Sign.find).toHaveBeenCalled()
expect($state.current.name).toBe state
describe '$scope.signs', ->
it 'should be automatically set if not passed to controller', ->
$scope = {}
controller = $controller 'AdminSignsCtrl',
$scope: $scope
Sign: Sign
signs: []
expect(Array.isArray $scope.signs).toEqual true
expect($scope.signs.length).toEqual 2
for i, sign of $scope.signs
expect(sign.position).toEqual parseInt i
it 'should be set if passed to controller', ->
$scope = {}
controller = $controller 'AdminSignsCtrl',
$scope: $scope
Sign: Sign
signs: [
{
position: 0
}
{
position: 1
}
{
position: 2
}
]
expect(Array.isArray $scope.signs).toEqual true
expect($scope.signs.length).toEqual 3
describe '$scope.{up,down,add,remove}', ->
$scope = {}
controller = null
beforeEach ->
$scope = {}
controller = $controller 'AdminSignsCtrl',
$scope: $scope
Sign: Sign
signs: [
{position: 0, name: 'position 0', $save: jasmine.createSpy('$save')}
{position: 1, name: 'position 1', $save: jasmine.createSpy('$save')}
{position: 2, name: 'position 2', $save: jasmine.createSpy('$save')}
]
it 'should add an sign', ->
$scope.add 1
expect(Array.isArray $scope.signs).toEqual true
expect($scope.signs.length).toEqual 4
it 'should save items on update', ->
$scope.add 1
$scope.signs[1].$save = jasmine.createSpy('$save')
$scope.doUpdate {}
expect($scope.signs[1].$save).toHaveBeenCalled()
it 'should delete items on update', ->
removedItem = $scope.signs[1]
removedItem.$delete = jasmine.createSpy('$delete')
$scope.remove 1
$scope.doUpdate {}
expect(removedItem.$delete).toHaveBeenCalled()
Example n°2
Directive
angular.module 'pepiniere-dashboard-sirf.comment'
.directive 'commentPanel', ->
restrict: 'E'
scope:
comment: '='
editable: '='
templateUrl: 'comment/directives/comment-panel/template.html'
link: ($scope) ->
$scope.isCollapsed = true
$scope.editing = false
$scope.switchCollapse = ->
$scope.isCollapsed = not $scope.isCollapsed
$scope.switchEdit = ->
$scope.editing = not $scope.editing
$scope.save = ->
$scope.comment.$save().then ->
$scope.editing = false
describe '[DIRECTIVE] commentPanel', ->
$compile = null
$q = null
$scope = null
$state = null
$rootScope = null
element = null
template = '<comment-panel comment="comment" editable="editable"></comment-panel>'
beforeEach ->
angular.module 'textAngular', []
beforeEach module 'pepiniere-dashboard-sirf.comment'
beforeEach inject (_$compile_, _$q_, _$rootScope_, _$injector_, _$state_) ->
$compile = _$compile_
$q = _$q_
$rootScope = _$rootScope_
$injector = _$injector_
$state = _$state_
beforeEach ->
$scope = $rootScope.$new()
def = $q.defer()
def.resolve {}
$scope.comment =
content: ''
$save: jasmine.createSpy('save').and.returnValue def.promise
$scope.editable = true
element = $compile(template) $scope
$rootScope.$digest()
it 'should inject template', ->
expect(element.html()).toContain 'class="panel"'
it 'should create child scope', ->
$child = $scope.$$childTail
expect($child).toBeDefined()
expect($child.isCollapsed).toBe true
expect($child.editing).toBe false
expect($child.switchCollapse).toBeDefined()
expect($child.switchEdit).toBeDefined()
expect($child.save).toBeDefined()
it 'should switch collapse', ->
$child = $scope.$$childTail
expect($child.isCollapsed).toBe true
$child.switchCollapse()
expect($child.isCollapsed).toBe false
it 'should switch edition', ->
$child = $scope.$$childTail
expect($child.editing).toBe false
$child.switchEdit()
expect($child.editing).toBe true
it 'should save', ->
$child = $scope.$$childTail
$child.switchEdit()
expect($child.editing).toBe true
$child.save()
$rootScope.$digest()
expect($child.editing).toBe false
expect($scope.comment.$save).toHaveBeenCalled()
Example n°3
Services
angular.module 'pepiniere-dashboard-sirf.board'
.factory 'BoardPim1Service', ($filter, $translate, GlobalFilterService) ->
computeDeliveryFeesChart = (entities, input) ->
skeleton =
data: [
{
key: $translate.instant 'widgets.deliveryFees.deliveryFees'
values: []
}
]
options:
...
return skeleton if not input or _.isEmpty input
entity = GlobalFilterService.get 'entity'
if not entity
entity = _.find entities, (item) -> item.position is 0
data = _.find input, (item) ->
return entity and entity.name.toLowerCase() is item.department.toLowerCase()
return skeleton if not data
skeleton.data[0].values = _.map data.items, (item) ->
x: new Date item.date
y: item.value
minValue = _.min _.map(skeleton.data[0].values, 'y')
minValue = 100 * Math.ceil(minValue * 0.5 / 100)
skeleton.options.chart.forceY = [ minValue ]
return skeleton
computeDeliveryFeesChart: computeDeliveryFeesChart
describe '[SERVICE] BoardPim1Service', ->
BoardPim1Service = null
GlobalFilterService = null
beforeEach module 'pepiniere-dashboard-sirf.filters'
beforeEach module 'pepiniere-dashboard-sirf.board'
beforeEach inject (_BoardPim1Service_, _GlobalFilterService_) ->
BoardPim1Service = _BoardPim1Service_
GlobalFilterService = _GlobalFilterService_
it 'should be defined', ->
expect(BoardPim1Service).toBeDefined()
describe 'computeChangeItChart', ->
entities = [{name: 'dir', position: 0}]
it 'should be defined', ->
expect(BoardPim1Service.computeDeliveryFeesChart).toBeDefined()
it 'should return an array object', ->
result = BoardPim1Service.computeDeliveryFeesChart entities, data
expect(result.data).toBeDefined()
expect(result.options).toBeDefined()
expect(result.data.length).toBe(1)
expect(result.data[0].values.length).toBe(12)
it 'should filter signs', ->
GlobalFilterService.put 'entity', {name: 'DIR', position: 1}
result = BoardPim1Service.computeDeliveryFeesChart entities, data
expect(result.data[0].values.length).toBe(12)
it 'should give a date formater', ->
result = BoardPim1Service.computeDeliveryFeesChart entities, data
fn = result.options.chart.xAxis.tickFormat
expect(fn).toBeDefined()
expect(fn(new Date(2015, 2,15))).toEqual 'Mar 15'
Unit testing
By Jérémie Drouet
Unit testing
- 1,122