11-1 Introduction to Testing in AngularJS
Key Concepts
- Unit Testing
- End-to-End Testing
- Test Frameworks
- Test Doubles
- Assertions
- Test Suites
- Test Runners
- Code Coverage
- Continuous Integration
- Mocking and Stubbing
1. Unit Testing
Unit testing involves testing individual components or units of code in isolation to ensure they work as expected. In AngularJS, this typically means testing controllers, services, directives, and filters.
Example:
describe('MyController', function() { var $scope; beforeEach(module('myApp')); beforeEach(inject(function($rootScope, $controller) { $scope = $rootScope.$new(); $controller('MyController', {$scope: $scope}); })); it('should initialize the title', function() { expect($scope.title).toBe('Hello, World!'); }); });
Imagine unit testing as checking each part of a machine (code unit) to ensure it functions correctly before assembling the entire machine.
2. End-to-End Testing
End-to-End (E2E) testing involves testing the entire application flow from start to finish to ensure all components work together as expected. In AngularJS, this is often done using tools like Protractor.
Example:
describe('Protractor Demo App', function() { it('should have a title', function() { browser.get('http://juliemr.github.io/protractor-demo/'); expect(browser.getTitle()).toEqual('Super Calculator'); }); });
Think of E2E testing as a simulation of a real-world scenario where you test the entire process, from placing an order to receiving a confirmation, to ensure everything works seamlessly.
3. Test Frameworks
Test frameworks provide the necessary tools and utilities to write and run tests. Popular frameworks for AngularJS include Jasmine and Mocha.
Example (Jasmine):
describe('A suite', function() { it('contains spec with an expectation', function() { expect(true).toBe(true); }); });
Consider test frameworks as the blueprint and tools for building a house. They provide the structure and utilities needed to construct and test the house (application).
4. Test Doubles
Test doubles are objects that replace real components during testing. They include mocks, stubs, and spies. These help isolate the unit being tested.
Example (using Jasmine spies):
describe('A spy', function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); }); it('tracks that the spy was called', function() { expect(foo.setBar).toHaveBeenCalled(); }); });
Think of test doubles as stand-ins for actors in a movie. They perform the same role as the real actors (components) but are used for specific scenes (tests) to ensure the main actor (unit) performs correctly.
5. Assertions
Assertions are statements that check if a condition is true. If the condition is false, the test fails. Assertions are fundamental to unit testing.
Example:
it('should be true', function() { expect(true).toBe(true); });
Consider assertions as checkpoints in a race. Each checkpoint (assertion) ensures the runner (code) is on the right track and meets the required conditions.
6. Test Suites
Test suites are collections of test cases that are related and grouped together. They help organize and manage tests.
Example:
describe('Math operations', function() { it('should add two numbers', function() { expect(1 + 1).toBe(2); }); it('should subtract two numbers', function() { expect(2 - 1).toBe(1); }); });
Think of test suites as chapters in a book. Each chapter (suite) contains related stories (test cases) that contribute to the overall narrative (application).
7. Test Runners
Test runners are tools that execute test suites and provide feedback on the results. Popular test runners for AngularJS include Karma and Jasmine.
Example (Karma configuration):
module.exports = function(config) { config.set({ basePath: '', frameworks: ['jasmine'], files: [ 'src/**/*.js', 'test/**/*.js' ], reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); };
Consider test runners as race organizers. They set up the race (test environment), start the race (execute tests), and report the results (feedback).
8. Code Coverage
Code coverage measures the percentage of code that is executed during testing. It helps identify untested parts of the codebase.
Example (using Istanbul):
{ "scripts": { "test": "nyc --reporter=html --reporter=text npm run test" } }
Think of code coverage as a map that shows which parts of a forest (codebase) have been explored (tested) and which parts remain uncharted.
9. Continuous Integration
Continuous Integration (CI) is a practice where code changes are automatically tested and integrated into the main codebase. Tools like Jenkins, Travis CI, and CircleCI are commonly used.
Example (Travis CI configuration):
language: node_js node_js: - "12" script: - npm test
Consider CI as an assembly line in a factory. Each new part (code change) is automatically tested (quality control) and added to the final product (codebase).
10. Mocking and Stubbing
Mocking and stubbing involve creating fake implementations of dependencies to isolate the unit being tested. This helps ensure that the test focuses on the unit's behavior.
Example (using Jasmine spies):
describe('A spy', function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); }); it('tracks that the spy was called', function() { expect(foo.setBar).toHaveBeenCalled(); }); });
Think of mocking and stubbing as using props in a theater. The props (mocks/stubs) replace real objects (dependencies) to ensure the actor (unit) performs correctly without relying on the real objects.