AngularJS
testing
strategies
Nate Peterson
@njpetersonPa
What’s not in this talk
What’s this talk about
Why do we care about
testing?
Tests help us fail fast
Tests give us confidence
Tests help us understand what
we’re doing and where we’re going
JavaScript is a dynamically typed
language with almost nohelp from
compiler
“One of the fundamental reasons
for choosing Angular is cited as that
it is built with testing in
mind.”
function MyController() {
var dep1 = new Dep1();
var dep2 = new Dep2();
//do something with dep1 and dep2
...
}
someModule.controller('MyController',
['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
...
$scope.aMethod = funct...
function AddCtrl() {
var operand1 = $(#operand1);
var operand2 = $(#operand2);
var result = $(#result);
this.add = functio...
var operand1 = $('<input type="text" id="operand1" />');
var operand2 = $('<input type="text" id="operand1" />');
var resu...
Controllers - The Angular way
function AddCtrl($scope) {
$scope.Calc = function() {
$scope.result = $scope.operand1 + $sco...
Controllers - The Angular way
var $scope = {};
var ctrl = $controller(‘AddCtrl’), {$scope: $scope };
$scope.operand1 = 1;
...
Two types of testing
that
compliment each other
unit testing
and
e2e testing
How much reality do you need
in your tests?
Knowing what to test is just as
important as how to test
Test all the
things
is not a strategy
“I get paid for code that works,
not for tests, so my philosophy is
to test as little as possible to reach
a given level o...
Focus on behaviors
rather than implementation details
Example – Testing a simple controller
app.controller('AddCtrl', function($scope) {
$scope.calc = function() {
$scope.result = $scope.operand1 + $scope.operand2;...
app.controller('AddCtrl', function($scope) {
$scope.calc = function() {
$scope.result = $scope.operand1 + $scope.operand2;...
app.controller('AddCtrl', function($scope) {
$scope.calc = function() {
$scope.result = $scope.operand1 + $scope.operand2;...
app.controller('AddCtrl', function($scope) {
$scope.calc = function() {
$scope.result = $scope.operand1 + $scope.operand2;...
Example – mocking $http
var app = angular.module('myApp', []);
app.controller('MoviesController', function($scope, $http) {
$http.get("/api/movies...
describe("myApp", function () {
beforeEach(module('myApp'));
describe("MoviesController", function () {
var scope, httpBac...
describe("myApp", function () {
beforeEach(module('myApp'));
describe("MoviesController", function () {
var scope, httpBac...
describe("myApp", function () {
beforeEach(module('myApp'));
describe("MoviesController", function () {
var scope, httpBac...
describe("myApp", function () {
beforeEach(module('myApp'));
describe("MoviesController", function () {
var scope, httpBac...
describe("myApp", function () {
beforeEach(module('myApp'));
describe("MoviesController", function () {
var scope, httpBac...
Example - mocking services
angular.module('myApp', [])
.factory('greeter', function () {
return 'Hello';
})
.factory('worldGreeter', function (greete...
angular.module('myApp', [])
.factory('greeter', function () {
return 'Hello';
})
.factory('worldGreeter', function (greete...
angular.module('myApp', [])
.factory('greeter', function () {
return 'Hello';
})
.factory('worldGreeter', function (greete...
angular.module('myApp', [])
.factory('greeter', function () {
return 'Hello';
})
.factory('worldGreeter', function (greete...
angular.module('myApp', [])
.factory('greeter', function () {
return 'Hello';
})
.factory('worldGreeter', function (greete...
Example – testing a directive
var app = angular.module('myApp', []);
app.directive('simpleDirective', function (){
return {
restrict: 'E',
template: '<d...
describe('Testing simpleDirective', function() {
var scope, elem, directive, compiled, html;
beforeEach(function () {
modu...
describe('Testing simpleDirective', function() {
var scope, elem, directive, compiled, html;
beforeEach(function (){
modul...
describe('Testing simpleDirective', function() {
var scope, elem, directive, compiled, html;
beforeEach(function (){
modul...
 
describe('Testing  simpleDirective', function() {  
 var scope, elem, directive, compiled, html;        
 beforeEach(fun...
 
describe('Testing  simpleDirective', function() {  
 var scope, elem, directive, compiled, html;        
 beforeEach(fun...
e2e testing / protractor
<body>    
   <h1>Sample</h1>    
   <div>
      Two Way Data Binding Sample      
      <br/><br/>      
      <input typ...
describe(demo page', function() {
it('should greet the user', function() {
browser.get('[some route]');
});
});
<body>    ...
describe(demo page', function() {
it('should greet the user', function() {
browser.get('[some route]');
element(by.model('...
describe(demo page', function() {
it('should greet the user', function() {
browser.get('[some route]');
element(by.model('...
describe(demo page', function() {
it('should greet the user', function() {
browser.get('[some route]');
element(by.model('...
What’s worked well so far
Use of a Mock API for e2e tests
What’s been hard
Bloated controllers
that lead to
bloated specs
Complicated unit test setup
Hard to read tests
Questions?
AngularJS
testing
strategies
Nate Peterson
@njpetersonPa
AngularJS Testing Strategies
AngularJS Testing Strategies
AngularJS Testing Strategies
Upcoming SlideShare
Loading in …5
×

AngularJS Testing Strategies

1,389 views

Published on

A question of why we test, what makes AngularJS easy to test, and strategies to test AngularJS.

Published in: Software, Technology, Education
1 Comment
3 Likes
Statistics
Notes
No Downloads
Views
Total views
1,389
On SlideShare
0
From Embeds
0
Number of Embeds
31
Actions
Shares
0
Downloads
32
Comments
1
Likes
3
Embeds 0
No embeds

No notes for slide
  • TDD (TDD is dead) – could be a whole talk on its own
    Grunt / Karma – though they do provide great value
  • Unit / e2e testing
    Strategies for testing
    Lessons learned
    Code
    examples
  • Strategic reasons
    Helps us fail fast
    Safety net – confidence
    Helps us understand
    Tactical reasons
    Dynamically typed language with almost no help from compiler
  • Strategic reasons
    Helps us fail fast
    Safety net – confidence
    Helps us understand
    Tactical reasons
    Dynamically typed language with almost no help from compiler
  • Strategic reasons
    Helps us fail fast
    Safety net – confidence
    Helps us understand
    Tactical reasons
    Dynamically typed language with almost no help from compiler
  • Strategic reasons
    Helps us fail fast
    Safety net – confidence
    Helps us understand
    Tactical reasons
    Dynamically typed language with almost no help from compiler
  • Javascript can be hard
  • What does this mean?
    - Separation concerns
  • Need a new image this is terrible
  • Pattern
    IOC
  • Show something where dependencies are not injected
  • DI in angular
  • That the controller doesn’t need the dom
  • Something that might be written in jquery
  • Something that we might write to test that.
    Lacks readability and hard to understand.
  • Maybe mention filters and directives
    Format expressions
  • What do you use it for…
    Why would you use it…
    How do you use it?
    Behaviors vs. correctness
    brittleness
  • Justin Searls
  • Tests what’s important
  • expect(greeting.getText()).toEqual(&amp;apos;Hello &amp;apos;Nate Peterson&amp;apos;);
  • AngularJS Testing Strategies

    1. 1. AngularJS testing strategies Nate Peterson @njpetersonPa
    2. 2. What’s not in this talk
    3. 3. What’s this talk about
    4. 4. Why do we care about testing?
    5. 5. Tests help us fail fast
    6. 6. Tests give us confidence
    7. 7. Tests help us understand what we’re doing and where we’re going
    8. 8. JavaScript is a dynamically typed language with almost nohelp from compiler
    9. 9. “One of the fundamental reasons for choosing Angular is cited as that it is built with testing in mind.”
    10. 10. function MyController() { var dep1 = new Dep1(); var dep2 = new Dep2(); //do something with dep1 and dep2 ... }
    11. 11. someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) { ... $scope.aMethod = function() { ... } ... }]); The Angular way
    12. 12. function AddCtrl() { var operand1 = $(#operand1); var operand2 = $(#operand2); var result = $(#result); this.add = function() { result = operand1 + operand2; } }
    13. 13. var operand1 = $('<input type="text" id="operand1" />'); var operand2 = $('<input type="text" id="operand1" />'); var result = $('<input type="text" id= "result" />'); var span = $('<span>'); $('body').html('<div class="ex1">') .find('div') .append(operand1) .append(operand2) .append(result); var ac = new AddCtrl(); operand1.val('1'); operand2.val('1'); ac.add(); expect(result.val()).toEqual('2'); $('body').empty();
    14. 14. Controllers - The Angular way function AddCtrl($scope) { $scope.Calc = function() { $scope.result = $scope.operand1 + $scope.operand2; } }
    15. 15. Controllers - The Angular way var $scope = {}; var ctrl = $controller(‘AddCtrl’), {$scope: $scope }; $scope.operand1 = 1; $scope.operand2 = 1; $scope.calc(); expect($scope.result).toEqual(2);
    16. 16. Two types of testing that compliment each other
    17. 17. unit testing and e2e testing
    18. 18. How much reality do you need in your tests?
    19. 19. Knowing what to test is just as important as how to test
    20. 20. Test all the things is not a strategy
    21. 21. “I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence…” -- Kent Beck
    22. 22. Focus on behaviors rather than implementation details
    23. 23. Example – Testing a simple controller
    24. 24. app.controller('AddCtrl', function($scope) { $scope.calc = function() { $scope.result = $scope.operand1 + $scope.operand2; } });
    25. 25. app.controller('AddCtrl', function($scope) { $scope.calc = function() { $scope.result = $scope.operand1 + $scope.operand2; } }); describe('adding 1 + 1', function() { beforeEach(module('myApp')); });
    26. 26. app.controller('AddCtrl', function($scope) { $scope.calc = function() { $scope.result = $scope.operand1 + $scope.operand2; } }); describe('adding 1 + 1', function() { beforeEach(module('myApp')); var ctrl, scope; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); ctrl = $controller('AddCtrl', { $scope: scope }); })); });
    27. 27. app.controller('AddCtrl', function($scope) { $scope.calc = function() { $scope.result = $scope.operand1 + $scope.operand2; } }); describe('adding 1 + 1', function() { beforeEach(module('myApp')); var ctrl, scope; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); ctrl = $controller('AddCtrl', { $scope: scope }); })); it('should equal 2', function() { scope.operand1 = 1; scope.operand2 = 1; scope.calc(); expect(scope.result).toEqual(2); }) });
    28. 28. Example – mocking $http
    29. 29. var app = angular.module('myApp', []); app.controller('MoviesController', function($scope, $http) { $http.get("/api/movies") .then(function (result) { $scope.movies = result.data; }); });
    30. 30. describe("myApp", function () { beforeEach(module('myApp')); describe("MoviesController", function () { var scope, httpBackend, http, controller; });
    31. 31. describe("myApp", function () { beforeEach(module('myApp')); describe("MoviesController", function () { var scope, httpBackend, http, controller; beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) { scope = $rootScope.$new(); httpBackend = $httpBackend; http = $http; controller = $controller; })); });
    32. 32. describe("myApp", function () { beforeEach(module('myApp')); describe("MoviesController", function () { var scope, httpBackend, http, controller; beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) { scope = $rootScope.$new(); httpBackend = $httpBackend; http = $http; controller = $controller; httpBackend.when("GET", "/api/movies") .respond([{}, {}, {}]); })); }); });
    33. 33. describe("myApp", function () { beforeEach(module('myApp')); describe("MoviesController", function () { var scope, httpBackend, http, controller; beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) { scope = $rootScope.$new(); httpBackend = $httpBackend; http = $http; controller = $controller; httpBackend.when("GET", "/api/movies") .respond([{}, {}, {}]); })); it('should GET movies', function () { httpBackend.expectGET('/api/movies'); controller('MoviesController', { $scope: scope, $http: http }); }); }); });
    34. 34. describe("myApp", function () { beforeEach(module('myApp')); describe("MoviesController", function () { var scope, httpBackend, http, controller; beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) { scope = $rootScope.$new(); httpBackend = $httpBackend; http = $http; controller = $controller; httpBackend.when("GET", "/api/movies") .respond([{}, {}, {}]); })); it('should GET movies', function () { httpBackend.expectGET('/api/movies'); controller('MoviesController', { $scope: scope, $http: http }); httpBackend.flush(); }); }); });
    35. 35. Example - mocking services
    36. 36. angular.module('myApp', []) .factory('greeter', function () { return 'Hello'; }) .factory('worldGreeter', function (greeter) { return greeter + ' World'; });
    37. 37. angular.module('myApp', []) .factory('greeter', function () { return 'Hello'; }) .factory('worldGreeter', function (greeter) { return greeter + ' World'; }); describe('worldGreeter', function () { beforeEach(inject(function (_worldGreeter_) { worldGreeter = _worldGreeter_; })); });
    38. 38. angular.module('myApp', []) .factory('greeter', function () { return 'Hello'; }) .factory('worldGreeter', function (greeter) { return greeter + ' World'; }); describe('worldGreeter', function () { beforeEach(inject(function (_worldGreeter_) { worldGreeter = _worldGreeter_; })); it('should work with mocked greeter', function () { expect(worldGreeter).toEqual('WAT World'); }); });
    39. 39. angular.module('myApp', []) .factory('greeter', function () { return 'Hello'; }) .factory('worldGreeter', function (greeter) { return greeter + ' World'; }); describe('worldGreeter', function () { beforeEach(module('myApp', function($provide) { $provide.value('greeter', 'WAT'); })); beforeEach(inject(function (_worldGreeter_) { worldGreeter = _worldGreeter_; })); it('should work with mocked greeter', function () { expect(worldGreeter).toEqual('WAT World'); }); });
    40. 40. angular.module('myApp', []) .factory('greeter', function () { return 'Hello'; }) .factory('worldGreeter', function (greeter) { return greeter + ' World'; }); describe('worldGreeter', function () { beforeEach(module('myApp', function ($provide) { $provide.decorator('greeter', function ($delegate) { return 'WAT'; }); })); beforeEach(inject(function (_worldGreeter_) { worldGreeter = _worldGreeter_; })); it('should work with mocked greeter', function () { expect(worldGreeter).toEqual('WAT World'); }); });
    41. 41. Example – testing a directive
    42. 42. var app = angular.module('myApp', []); app.directive('simpleDirective', function (){ return { restrict: 'E', template: '<div>{{value}}</div>', scope: { value: '=' } }; });
    43. 43. describe('Testing simpleDirective', function() { var scope, elem, directive, compiled, html; beforeEach(function () { module('myApp‘); }); }); it('Should set the text of the element to whatever was passed.', function() { }); });
    44. 44. describe('Testing simpleDirective', function() { var scope, elem, directive, compiled, html; beforeEach(function (){ module('myApp'); html = '<simple-directive value="abc"></simple-directive>'; }); it('Should set the text of the element to whatever was passed.', function() { }); });
    45. 45. describe('Testing simpleDirective', function() { var scope, elem, directive, compiled, html; beforeEach(function (){ module('myApp'); html = ‘<simple-directive value="abc"></simple-directive>'; inject(function($compile, $rootScope) { scope = $rootScope.$new(); elem = angular.element(html); compiled = $compile(elem); compiled(scope); }); }); it('Should set the text of the element to whatever was passed.', function() { }); });
    46. 46.   describe('Testing  simpleDirective', function() {    var scope, elem, directive, compiled, html;          beforeEach(function (){         module('myApp');             html = ‘<simple-directive value="abc"></simple-directive>';                inject(function($compile, $rootScope) {                    scope = $rootScope.$new();              elem = angular.element(html);              compiled = $compile(elem);              compiled(scope);         });    });    it('Should set the text of the element to whatever was passed.',     function() {         expect(elem.text()).toBe('blah');  }); });
    47. 47.   describe('Testing  simpleDirective', function() {    var scope, elem, directive, compiled, html;          beforeEach(function (){         module('myApp');             html = ‘<simple-directive value="abc"></simple-directive>';                inject(function($compile, $rootScope) {                    scope = $rootScope.$new();              elem = angular.element(html);              compiled = $compile(elem);              compiled(scope);         });    });    it('Should set the text of the element to whatever was passed.',     function() {          scope.abc = 'blah'; scope.$digest();     expect(elem.text()).toBe('blah');    }); });
    48. 48. e2e testing / protractor
    49. 49. <body>        <h1>Sample</h1>        <div>       Two Way Data Binding Sample             <br/><br/>             <input type="text" ng-model="name" />             <span ng-show="name"><h4>Hello {{name}}</h4></span>    </div>   </body>
    50. 50. describe(demo page', function() { it('should greet the user', function() { browser.get('[some route]'); }); }); <body>        <h1>Sample</h1>        <div>       Two Way Data Binding Sample             <br/><br/>             <input type="text" ng-model="name" />             <span ng-show="name"><h4>Hello {{name}}</h4></span>    </div>   </body>
    51. 51. describe(demo page', function() { it('should greet the user', function() { browser.get('[some route]'); element(by.model('name')).sendKeys('Nate Peterson'); }); }); <body>        <h1>Sample</h1>        <div>       Two Way Data Binding Sample             <br/><br/>             <input type="text" ng-model="name" />             <span ng-show="name"><h4>Hello {{name}}</h4></span>    </div>   </body>
    52. 52. describe(demo page', function() { it('should greet the user', function() { browser.get('[some route]'); element(by.model('name')).sendKeys('Nate Peterson'); var greeting = element(by.binding('name')); }); }); <body>        <h1>Sample</h1>        <div>       Two Way Data Binding Sample             <br/><br/>             <input type="text" ng-model="name" />             <span ng-show="name"><h4>Hello {{name}}</h4></span>    </div>   </body>
    53. 53. describe(demo page', function() { it('should greet the user', function() { browser.get('[some route]'); element(by.model('name')).sendKeys('Nate Peterson'); var greeting = element(by.binding('name')); expect(greeting.getText()).toEqual('Hello 'Nate Peterson'); }); }); <body>        <h1>Sample</h1>        <div>       Two Way Data Binding Sample             <br/><br/>             <input type="text" ng-model="name" />             <span ng-show="name"><h4>Hello {{name}}</h4></span>    </div>   </body>
    54. 54. What’s worked well so far
    55. 55. Use of a Mock API for e2e tests
    56. 56. What’s been hard
    57. 57. Bloated controllers that lead to bloated specs
    58. 58. Complicated unit test setup
    59. 59. Hard to read tests
    60. 60. Questions?
    61. 61. AngularJS testing strategies Nate Peterson @njpetersonPa

    ×