AngularJS Testing using Jasmine
Developer @HPSoftware
HP Software Blog: http://www.hp.com/go/swdevblog
“AngularJS was designed from ground up to be testable”
http://angularjs.org/
Agenda
- Jasmine
- AngularJS
Jasmine
Why WRITE UNIT TESTS?
Partial list
-
Verify code correctness
- Fast feedback
-
Assure build quality
- Provide documentation for the source code
- Uncovering bugs early
UNIT TESTING FRAMEWORKS
Dozens of frameworks
Hello Jasmine
Jasmine is a behavior-driven development framework for testing your JavaScript code.
Jasmine is built in JavaScript and must be included into a JS environment, such as a web page, in order to run .
It does not depend on any other JavaScript frameworks.
It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
Maintained by Pivotal Labs on Github
TEST STRUCTURE
describe() {
beforeEach()
afterEach()
it() {
expect().matcher()
}
}
SUITES AND SPECS
Suites describe the component under test
Specs describe what each function tests
Nested suites are supported
describe("Distance Converter", function() {
it("converts inches to centimeters", function(){
expect(Convert(12, "in").to("cm")).toEqual(30.48);
});
})
SUITES and specs
Running selected specs can be done with iit
Disabling selected specs can be done with xit
Running selected suites can be done with ddescribe
Disabling selected specs can be done with xdescribe
iit has higher priority over ddescribe
Don’t forget to fail the build if everything is ignored by mistake (iit, ddescribe)
EXPECTATIONS and matchers
Expectations
Expectations are built with the function expect which takes a value, called the actual. It is chained with a matcher function, which takes the expected value.
Matchers
Each matcher implements a boolean comparison between the actual value and the expected value
a = true;
expect(a).toBe(true);
Built-in matchers
CUSTOM matchers
Write expressive custom matchers to fit your needs.
Add them to your suite using addMatchers .
Matchers can be added globally when added in
a beforeEach function outside the scope of a suite
See fiddle
beforeEach(function() {
this.addMatchers({
toBeLessThan: function(expected) {
return this.actual < expected;
}
});
})
spies
Jasmine integrates spies that permit many spying, mocking, and faking behaviors.
A 'spy' replaces the function it is spying on
.
Jasmine spies allow you to test if functions have been called, and to inspect the arguments they were called with.
spy creation
- spyOn + demonstrating spy matchers
- createSpy
- createSpyObj
spy usage guidelines
Use
spyOn
for already existing objects which
you need to spy on a specific method, e.g.
console.log
Use createSpy for functions called by the code under test with no return value, usually callbacks
Use createSpyObj for interactions of code under test with dependencies, usually other classes
See fiddle
spy training
Now that you’ve created spy functions you can train them to do neat tricks
Asynchronous specs
Jasmine supports running specs that require testing asynchronous operations .
Using the commands waits , waitsFor and runs the test can wait for an async condition to be met and run verifications afterwards
asynchronous specs
waits allows you to pause the spec for a fixed period of time
waitsFor takes a latch function, a failure message, and a timeout.
The latch function polls until it returns true or the timeout expires, whichever comes first. If the timeout expires, the spec fails with the error message.
Once the asynchronous conditions have been met, another runs block defines final test behavior. This is usually expectations based on state after the asynch call returns.
asynchronous specs
waits , waitsFor and runs functions are non-blocking.
The runs function callback is executed only after waits and waitsFor complete successfully
See fiddle
mocking the clock
The Jasmine Mock Clock is available for test suites that need the ability to use setTimeout or setInterval callbacks.
It makes the timer callbacks synchronous, thus making them easier to test.
See fiddle
MATCHING anything
jasmine.any takes a constructor or “class” name as an expected value.
It returns true if the constructor matches the constructor of the actual value.
See fiddle
exercise
AngularJS Testing
AngularJS
AngularJS was written with testing in mind “so there is no excuse for not testing ”
This MVW framework's separation of concerns aids with unit testing
On top of that you get dependency injection which paves the way for using spies
angular mocks
module
inject
$httpBackend
suitable for unit testing applications that use the $http service
- Avoid XHR or JSONP requests to the real server
- Use hard coded responses
- Control when the response is returned by calling flush
This feature is implemented by Injecting a mock $ httpBackend implementation to the $http service
See fiddle
additional helpers
$exceptionHandlerProvider
Configures the mock implementation $exceptionHandler to rethrow or log errors
$logProvider
additional helpers
See fiddle
mock injection
Angular elements such as controllers receive a list of dependencies injected by providers.
Angular modules configuration code must be initialized for tests by calling
angular.mock.module('module-name');
This call collects the configuration information which will be used when the injector is created
It does not initialize the module injectors.
mock injection
After
configuring the module dependencies as above you can
use
$provide
to return stub implementation for selected dependencies
Only on the first call to
inject(angular-component )
the application "bootstrap" takes place since the html is not loaded and there's no ng-app
mock injection
Declaring substitute implementations for DI with $provide in tests must be done before any inject calls since calling inject initializes all injectors and then it is forbidden to add/define new modules.
Since the real modules are loaded by the browser, when injecting a component it will use the real dependencies. This is not desirable for a unit test since the unit's isolation is broken. You must make sure that mock dependencies are used
promises
The documentation for testing promises is hidden inside the
$q
page
The trick is that you have to call scope.$apply () for promise resolution.
See fiddle
filters
Filters are instantiated with Angular’s $filter service
Additionally filter functions are registered with the $injector under the filter name suffixed with 'Filter '.
Predefined and custom filters can be injected using the syntax filterName Filter
See fiddle
controllers
This allows injecting dependencies during creation.
Since the controller’s interface is its scope it is the main test subject.
See fiddle
services
Services must be injected into the
test, there is no $service match for $controller
Injecting service dependencies requires replacing the original values used by Angular’s $inject service with mock ones
Use $provide for registering new providers
See fiddle
service providers
When you want to test the internal
functions of a service provider you need to inject it to a configuration block
of another Angular test module you create in the test code itself
Note that you still need to inject something for the module “bootstrap”
See fiddle
directives
Tests
compile directives using
$compile
.
During test execution we compile given DOM structure and use jQuery to test whether the component works as expected.
Note , that DOM structure should not be appended to the document. That makes the tests faster, because browser does not have to render it. So unless it’s necessary ( eg . you need to know some computed property of some DOM element), don’t attach nodes to the document .
directive preprocessing
Directive
templates can be
inlined
in the html or written in an
external file which is fetched from the server
In order to avoid fetching an external template you should pre-load the template into $ templateCache before the test starts
An example implementation is Karma's html2js preprocessor which defines a module with the name of the html file and adds it to the $ templateCache
directives
See fiddle
exercise
test runners
Jasmine is built in JavaScript and must be included into a JS environment, such as a web page, in order to run
Karma is a test runner with built in adapters to commonly used JS unit testing FWs such as Jasmine, QUnit , Mocha and more.
karma features
- Execute tests on every save by watching source files
-
Smooth Jenkins integration thru grunt plugins & singleRun mode
-
Built in JUnit reporter
-
Built in coverage reporter (Istanbul)
-
Allows testing source files with RequireJS dependencies
- Runs test suites simultaneously on different browsers, checking code validity on different JS engines
AngularJS Testing
By Eitan Peer
AngularJS Testing
- 9,862