Skip to content

Instantly share code, notes, and snippets.

@benlesh
Last active January 14, 2022 23:52
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save benlesh/8846528 to your computer and use it in GitHub Desktop.
Save benlesh/8846528 to your computer and use it in GitHub Desktop.
Angular - Basics of Unit Testing a Controller
var app = angular.module('myApp', []);
/* Set up a simple controller with a few
* examples of common actions a controller function
* might set up on a $scope. */
app.controller('MainCtrl', function($scope, someService) {
//set some properties
$scope.foo = 'foo';
$scope.bar = 'bar';
//add a simple function.
$scope.test1 = function (){
$scope.foo = $scope.foo + '!!!';
};
//set up a $watch.
$scope.$watch('bar', function (v){
$scope.baz = v + 'baz';
});
//make a call to an injected service.
$scope.test2 = function (){
//an async call returning a promise that
//inevitably returns a value to a property.
someService.someAsyncCall($scope.foo)
.then(function(data) {
$scope.fizz = data;
});
};
});
/* Simple service example.
* This is a service created just to use as an example of
* some simple service that is making some asynchronous call.
* A real-life example of something like this would be a
* service that is making $http or $resource calls, perhaps. */
app.factory('someService', function ($timeout, $q){
return {
// simple method to do something asynchronously.
someAsyncCall: function (x){
var deferred = $q.defer();
$timeout(function (){
deferred.resolve(x + '_async');
}, 100);
return deferred.promise;
}
};
});
<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular-mocks.js"></script>
<!-- your angular app code -->
<script src="app.js"></script>
<!-- your Jasmine specs (tests) -->
<script src="specs.js"></script>
</head>
<body>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
describe('Testing a controller', function() {
var $scope, ctrl, $timeout;
/* declare our mocks out here
* so we can use them through the scope
* of this describe block.
*/
var someServiceMock;
// This function will be called before every "it" block.
// This should be used to "reset" state for your tests.
beforeEach(function (){
// Create a "spy object" for our someService.
// This will isolate the controller we're testing from
// any other code.
// we'll set up the returns for this later
someServiceMock = jasmine.createSpyObj('someService', ['someAsyncCall']);
// load the module you're testing.
module('myApp');
// INJECT! This part is critical
// $rootScope - injected to create a new $scope instance.
// $controller - injected to create an instance of our controller.
// $q - injected so we can create promises for our mocks.
// _$timeout_ - injected to we can flush unresolved promises.
inject(function($rootScope, $controller, $q, _$timeout_) {
// create a scope object for us to use.
$scope = $rootScope.$new();
// set up the returns for our someServiceMock
// $q.when('weee') creates a resolved promise to "weee".
// this is important since our service is async and returns
// a promise.
someServiceMock.someAsyncCall.andReturn($q.when('weee'));
// assign $timeout to a scoped variable so we can use
// $timeout.flush() later. Notice the _underscore_ trick
// so we can keep our names clean in the tests.
$timeout = _$timeout_;
// now run that scope through the controller function,
// injecting any services or other injectables we need.
// **NOTE**: this is the only time the controller function
// will be run, so anything that occurs inside of that
// will already be done before the first spec.
ctrl = $controller('MainCtrl', {
$scope: $scope,
someService: someServiceMock
});
});
});
/* Test 1: The simplest of the simple.
* here we're going to test that some things were
* populated when the controller function whas evaluated. */
it('should start with foo and bar populated', function() {
//just assert. $scope was set up in beforeEach() (above)
expect($scope.foo).toEqual('foo');
expect($scope.bar).toEqual('bar');
});
/* Test 2: Still simple.
* Now let's test a simple function call. */
it('should add !!! to foo when test1() is called', function (){
//set up.
$scope.foo = 'x';
//make the call.
$scope.test1();
//assert
expect($scope.foo).toEqual('x!!!');
});
/* Test 3: Testing a $watch()
* The important thing here is to call $apply()
* and THEN test the value it's supposed to update. */
it('should update baz when bar is changed', function (){
//change bar
$scope.bar = 'test';
//$apply the change to trigger the $watch.
$scope.$apply();
//assert
expect($scope.baz).toEqual('testbaz');
});
/* Test 4: Testing an asynchronous service call.
Since we've mocked the service to return a promise
(just like the original service did), we need to do a little
trick with $timeout.flush() here to resolve our promise so the
`then()` clause in our controller function fires.
This will test to see if the `then()` from the promise is wired up
properly. */
it('should update fizz asynchronously when test2() is called', function (){
// just make the call
$scope.test2();
// asser that it called the service method.
expect(someServiceMock.someAsyncCall).toHaveBeenCalled();
// call $timeout.flush() to flush the unresolved dependency from our
// someServiceMock.
$timeout.flush();
// assert that it set $scope.fizz
expect($scope.fizz).toEqual('weee');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment