{"id":19346730,"url":"https://github.com/puigcerber/angular-unit-testing","last_synced_at":"2025-10-12T08:05:23.382Z","repository":{"id":32712759,"uuid":"36302388","full_name":"Puigcerber/angular-unit-testing","owner":"Puigcerber","description":":star2: Guidelines and patterns for unit testing AngularJS apps.","archived":false,"fork":false,"pushed_at":"2018-02-23T11:42:21.000Z","size":20,"stargazers_count":168,"open_issues_count":0,"forks_count":41,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-06-14T19:36:04.765Z","etag":null,"topics":["angular","angularjs","testing","unit-testing"],"latest_commit_sha":null,"homepage":"https://twitter.com/Puigcerber","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Puigcerber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-26T14:44:12.000Z","updated_at":"2024-11-29T17:07:12.000Z","dependencies_parsed_at":"2022-09-08T17:01:24.813Z","dependency_job_id":null,"html_url":"https://github.com/Puigcerber/angular-unit-testing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Puigcerber/angular-unit-testing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Puigcerber%2Fangular-unit-testing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Puigcerber%2Fangular-unit-testing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Puigcerber%2Fangular-unit-testing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Puigcerber%2Fangular-unit-testing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Puigcerber","download_url":"https://codeload.github.com/Puigcerber/angular-unit-testing/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Puigcerber%2Fangular-unit-testing/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010795,"owners_count":26084807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angularjs","testing","unit-testing"],"created_at":"2024-11-10T04:12:09.459Z","updated_at":"2025-10-12T08:05:23.350Z","avatar_url":"https://github.com/Puigcerber.png","language":"JavaScript","readme":"# AngularJS Unit Testing\r\n\r\nUnit testing, as the name implies, is about testing individual units of code.\r\n\r\n## About\r\n\r\nThis guide tries to show a few patterns and guidelines to help us with unit testing Angular applications after setting\r\nsome minimum configuration.\r\n\r\nA more readable version was [published on Airpair](https://www.airpair.com/angularjs/posts/unit-testing-angularjs-applications)\r\nand won the \"Best AngularJS post\" category of the 2015 Developer Writing Competition.\r\nIt has been shared more than 600 times on social media.\r\n\r\nThis repository contains also a complimentary [example project](example) with 100% test coverage.\r\n\r\n## Table of Contents\r\n\r\n* [Configuring](#configuring)\r\n  * [ngMock](#ngmock)\r\n  * [Karma](#karma)\r\n    * [Templates](#templates)\r\n    * [Coverage](#coverage)\r\n* [Mocking](#mocking)\r\n  * [Using $provide](#using-provide)\r\n  * [Creating a provider mock](#creating-a-provider-mock)\r\n* [Spying](#spying)\r\n* [Testing](#testing)\r\n  * [Controllers](#controllers)\r\n  * [Services](#services)\r\n  * [Directives](#directives)\r\n  * [Providers](#providers)\r\n\r\n## Configuring\r\n\r\n### ngMock\r\n\r\nAngular provides [ngMock](https://docs.angularjs.org/api/ngMock) to inject and mock services into unit tests. And one\r\nof the most useful parts of `ngMock`  is `$httpBackend` which lets us mock XHR requests.\r\n\r\n```\r\n$ npm install angular-mocks --save-dev\r\n```\r\n\r\n### Karma\r\n\r\n[Karma](http://karma-runner.github.io/) is a test runner which allow us to execute tests in multiple browsers.\r\n\r\n```\r\n# Install Karma:\r\n$ npm install karma --save-dev\r\n\r\n# Install plugins that our project needs:\r\n$ npm install karma-jasmine jasmine-core karma-chrome-launcher --save-dev\r\n```\r\n\r\nAfter installing it we need to create a configuration\r\nfile running `karma init`.\r\n\r\nAnd set the list of files that need to be loaded in the browser.\r\n\r\n```js\r\nmodule.exports = function(config) {\r\n  config.set({\r\n    files: [\r\n      'node_modules/angular/angular.js',\r\n      'node_modules/angular-mocks/angular-mocks.js',\r\n      'src/**/*.js'\r\n    ]\r\n  });\r\n};\r\n```\r\n\r\n#### Templates\r\n\r\nWhen testing directives we need to set up Karma to serve our templates.\r\n\r\nAnd for that we use a preprocessor to convert HTML into JS string.\r\n[ng-html2js](https://github.com/karma-runner/karma-ng-html2js-preprocessor) creates a \"templates\" module and put the HTML\r\n into the `$templateCache`.\r\n\r\n ```\r\n$ npm install karma-ng-html2js-preprocessor --save-dev\r\n```\r\n\r\nWe need to add some lines to the Karma configuration.\r\n\r\n```js\r\nmodule.exports = function(config) {\r\n  config.set({\r\n    preprocessors: {\r\n      'src/**/*.html': ['ng-html2js']\r\n    },\r\n\r\n    files: [\r\n      'node_modules/angular/angular.js',\r\n      'node_modules/angular-mocks/angular-mocks.js',\r\n      'src/**/*.js',\r\n      'src/**/*.html',\r\n    ],\r\n\r\n    ngHtml2JsPreprocessor: {\r\n      stripPrefix: 'src/',\r\n      moduleName: 'templates'\r\n    }\r\n  });\r\n};\r\n```\r\n\r\n#### Coverage\r\n\r\nIt's great to identify which parts of our code are lacking test coverage and generate reports with\r\n[Istanbul](https://github.com/gotwarlost/istanbul), that calculates the percentage of code accessed by tests.\r\n\r\n```\r\n$ npm install karma karma-coverage --save-dev\r\n```\r\n\r\nIn the configuration file we need to exclude spec and mock files from the report.\r\n\r\n```js\r\nmodule.exports = function(config) {\r\n  config.set({\r\n    preprocessors: {\r\n      'src/**/!(*.mock|*.spec).js': ['coverage']\r\n    },\r\n\r\n    reporters: ['progress', 'coverage'],\r\n\r\n    coverageReporter: {\r\n      type : 'html',\r\n      // output coverage reports\r\n      dir : 'coverage/'\r\n    }\r\n  });\r\n};\r\n```\r\n\r\n## Mocking\r\n\r\nTo test the functionality of a piece of code in isolation we need to mock any dependency.\r\n\r\nAngular is written with testability in mind and come with [dependency injection](https://docs.angularjs.org/guide/di)\r\nbuilt in, what helps us to achieve decoupling and therefore to test our objects in isolation.\r\n\r\nA service (service, factory, value, constant, or provider) is the most common type of dependency in Angular applications\r\nand they are defined via providers.\r\n\r\nThe [injector](https://docs.angularjs.org/api/auto/service/$injector) retrieves object instances as defined by provider.\r\nSo there are at least two ways we can mock our services.\r\n\r\n### Using $provide\r\n\r\nWe provide our own implementation in a `beforeEach` Jasmine block.\r\n\r\n```js\r\nvar myService;\r\nbeforeEach(module(function($provide) {\r\n  myService = {\r\n    syncCall: function() {},\r\n    asyncCall: function() {}\r\n  };\r\n  $provide.value('myService', myService);\r\n}));\r\n```\r\n\r\n### Creating a provider mock\r\n\r\nWe create the implementation in a separate `my-service.service.mock.js` file so we can reuse it.\r\n\r\n```js\r\nangular.module('myServiceMock', [])\r\n  .provider('myService', function() {\r\n    this.$get = function() {\r\n      return {\r\n        syncCall: function() {},\r\n        asyncCall: function() {}\r\n      };\r\n    };\r\n  });\r\n```\r\n\r\nAnd later we load the mocked module in a `beforeEach` Jasmine block inside of our test suite.\r\n\r\n```js\r\nbeforeEach(module('myServiceMock'));\r\n\r\nvar myService;\r\nbeforeEach(inject(function(_myService_) {\r\n  myService = _myService_;\r\n}));\r\n```\r\n\r\nIt's good to **load the mocked modules after** the module we are testing to be sure the mock overrides the actual implementation.\r\n\r\n## Spying\r\n\r\nUsing Jasmine we can [spy](http://jasmine.github.io/2.0/introduction.html#section-Spies) on functions tracking calls and\r\narguments passed.\r\n\r\n```js\r\nvar myService;\r\nbeforeEach(inject(function(_myService_) {\r\n  myService = _myService_;\r\n  spyOn(myService, 'syncCall').and.callThrough();\r\n}));\r\n```\r\n\r\nThis comes handy in particular when the original implementation returned a promise.\r\n\r\n```js\r\nvar myService;\r\nvar deferred;\r\nbeforeEach(inject(function($q, _myService_) {\r\n  myService = _myService_;\r\n  deferred = $q.defer();\r\n  spyOn(myService, 'asyncCall').and.returnValue(deferred.promise);\r\n}));\r\n```\r\n\r\nAs we can emulate its behavior and later resolve or reject the [promise](https://docs.angularjs.org/api/ng/service/$q)\r\nin our specs to test success and error calls.\r\n\r\n```js\r\ndeferred.resolve();\r\ndeferred.reject();\r\n```\r\n\r\n## Testing\r\n\r\n### Controllers\r\n\r\nControllers are easy to test as long as we don't manipulate the DOM and functions have a single and clear purpose.\r\n\r\nWe should test for the state, sync and async calls to services, and events.\r\n\r\n```js\r\ndescribe('Controller: MyController', function() {\r\n\r\n  beforeEach(module('myControllerModule'));\r\n  beforeEach(module('myServiceMock'));\r\n\r\n  var myService;\r\n  var deferred;\r\n  // Mock services and spy on methods\r\n  beforeEach(inject(function($q, _myService_) {\r\n    deferred = $q.defer();\r\n    myService = _myService_;\r\n    spyOn(myService, 'syncCall').and.callThrough();\r\n    spyOn(myService, 'asyncCall').and.returnValue(deferred.promise);\r\n  }));\r\n\r\n  var MyController;\r\n  var scope;\r\n  // Initialize the controller and a mock scope.\r\n  beforeEach(inject(function($rootScope, $controller) {\r\n    scope = $rootScope.$new();\r\n    spyOn(scope, '$emit');\r\n    MyController = $controller('MyController', {\r\n      $scope: scope,\r\n      myService: myService\r\n    });\r\n  }));\r\n\r\n  describe('State', function() {\r\n\r\n    it('should expose myProperty to the view', function() {\r\n      expect(MyController.myProperty).toBeDefined();\r\n      // We can use Angular helpers.\r\n      expect(angular.isArray(MyController.myArray)).toBe(true);\r\n      expect(angular.isObject(MyController.myObject)).toBe(true);\r\n      expect(angular.isNumber(MyController.myNumber)).toBe(true);\r\n    });\r\n\r\n    it('should expose a method to change myProperty', function() {\r\n      expect(MyController.changeProperty).toBeDefined();\r\n      expect(angular.isFunction(MyController.changeProperty)).toBe(true);\r\n    });\r\n\r\n    it('should change myProperty', function() {\r\n      MyController.changeProperty(true);\r\n      expect(MyController.myProperty).toBe(true);\r\n      MyController.changeProperty(false);\r\n      expect(MyController.myProperty).toBe(false);\r\n    });\r\n\r\n  });\r\n\r\n  describe('Synchronous calls', function() {\r\n\r\n    it('should call syncCall on myService', function() {\r\n      expect(myService.syncCall).toHaveBeenCalled();\r\n      expect(myService.syncCall.calls.count()).toBe(1);\r\n    });\r\n\r\n  });\r\n\r\n  describe('Asynchronous calls', function() {\r\n\r\n    it('should call asyncCall on myService', function() {\r\n      expect(myService.asyncCall).toHaveBeenCalled();\r\n      expect(myService.asyncCall.calls.count()).toBe(1);\r\n    });\r\n\r\n    it('should do something on success', function() {\r\n      var data = ['something', 'on', 'success'];\r\n      deferred.resolve(data);\r\n      scope.$digest();\r\n      // Check for state on success.\r\n      expect(MyController.myArray).toBe(data);\r\n    });\r\n\r\n    it('should do something on error', function() {\r\n      deferred.reject(400);\r\n      scope.$digest();\r\n      // Check for state on error.\r\n      expect(MyController.hasError).toBe(true);\r\n    });\r\n\r\n  });\r\n\r\n  describe('Events', function() {\r\n\r\n    it('should emit an event', function() {\r\n      expect(scope.$emit).toHaveBeenCalledWith('my-event');\r\n      expect(scope.$emit.calls.count()).toBe(1);\r\n    });\r\n\r\n    it('should do something when some-event is caught', inject(function($rootScope) {\r\n      $rootScope.$broadcast('some-event');\r\n      // Check for state after event is caught.\r\n      expect(MyController.myNumber).toBe(1);\r\n    }));\r\n\r\n  });\r\n\r\n});\r\n```\r\n\r\n### Services\r\n\r\nThe complexity of testing services comes from the use of promises and their resolution.\r\n\r\nIn addition of testing calls to other services, we should test for the output of our methods and HTTP requests.\r\nBut as we don't want to send XHR requests to a real server we use\r\n[$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).\r\n\r\n```js\r\ndescribe('Service: myService', function() {\r\n\r\n  beforeEach(module('myServiceModule'));\r\n\r\n  var myService;\r\n  var httpBackend;\r\n  beforeEach(inject(function($httpBackend, _myService_) {\r\n    httpBackend = $httpBackend;\r\n    myService = _myService_;\r\n  }));\r\n\r\n  describe('Output of methods', function() {\r\n\r\n    it('should return the product of all positive integers less than or equal to n', function() {\r\n      expect(myService.factorial(0)).toBe(1);\r\n      expect(myService.factorial(5)).toBe(120);\r\n      expect(myService.factorial(10)).toBe(3628800);\r\n    });\r\n\r\n  });\r\n\r\n  describe('HTTP calls', function() {\r\n\r\n    afterEach(function() {\r\n      httpBackend.verifyNoOutstandingExpectation();\r\n      httpBackend.verifyNoOutstandingRequest();\r\n    });\r\n\r\n    it('should call the API', function() {\r\n      httpBackend.expectGET(/\\/api\\/things/).respond('');\r\n      myService.getThings();\r\n      httpBackend.flush();\r\n    });\r\n\r\n    it('should return an array of things on success', function() {\r\n      var response = ['one thing', 'another thing'];\r\n      var myThings = [];\r\n      var errorStatus = '';\r\n      var handler = {\r\n        success: function(data) {\r\n          myThings = data;\r\n        },\r\n        error: function(data) {\r\n          errorStatus = data;\r\n        }\r\n      };\r\n      spyOn(handler, 'success').and.callThrough();\r\n      spyOn(handler, 'error').and.callThrough();\r\n\r\n      httpBackend.whenGET(/\\/api\\/things/).respond(response);\r\n      myService.getThings().then(handler.success, handler.error);\r\n      httpBackend.flush();\r\n\r\n      expect(handler.success).toHaveBeenCalled();\r\n      expect(myThings).toEqual(response);\r\n      expect(handler.error).not.toHaveBeenCalled();\r\n      expect(errorStatus).toEqual('');\r\n    });\r\n\r\n  });\r\n\r\n});\r\n```\r\n\r\n### Directives\r\n\r\nDirectives are a bit more complex to test as we need to [$compile](https://docs.angularjs.org/api/ng/service/$compile)\r\nthem manually to test the compiled DOM.\r\n\r\nAdditionally we use [angular.element](https://docs.angularjs.org/api/ng/function/angular.element) and the provided\r\njQuery or jqLite methods to manipulate the DOM.\r\n\r\nAnd if needed we can test the directive controller grabbing an instance.\r\n\r\n```js\r\ndescribe('Directive: myDirective', function() {\r\n\r\n  beforeEach(module('myDirectiveModule'));\r\n  beforeEach(module('templates'));\r\n\r\n  var element;\r\n  var scope;\r\n  beforeEach(inject(function($rootScope, $compile) {\r\n    scope = $rootScope.$new();\r\n    element = angular.element('\u003cmy-directive something=\"thing\"\u003e\u003c/my-directive\u003e');\r\n    element = $compile(element)(scope);\r\n    scope.thing = {name: 'My thing'};\r\n    scope.$apply();\r\n  }));\r\n\r\n  it('should render something', function() {\r\n    var h1 = element.find('h1');\r\n    expect(h1.text()).toBe('My thing');\r\n  });\r\n\r\n  it('should update the rendered text when scope changes', function() {\r\n    scope.thing.name = 'My new thing';\r\n    scope.$apply();\r\n    var h1 = element.find('h1');\r\n    expect(h1.text()).toBe('My new thing');\r\n  });\r\n\r\n  describe('Directive controller', function() {\r\n\r\n    var controller;\r\n    beforeEach(function() {\r\n      controller = element.controller('myDirective');\r\n    });\r\n\r\n    it('should do something', function() {\r\n      expect(controller.doSomething).toBeDefined();\r\n      controller.doSomething();\r\n      expect(controller.something.name).toBe('Do something');\r\n    });\r\n\r\n  });\r\n\r\n});\r\n```\r\n\r\n### Providers\r\n\r\nProviders are the toughest to test as we need to intercept them before they are injected.\r\n\r\nLater we can test them as any other service.\r\n\r\n```js\r\ndescribe('Provider: myProvider', function() {\r\n\r\n  beforeEach(module('myProviderModule'));\r\n\r\n  var myProvider;\r\n  beforeEach(function() {\r\n    // Intercept the provider.\r\n    module(function(_myProvider_) {\r\n      myProvider = _myProvider_;\r\n      });\r\n    // Trigger the injection.\r\n    inject();\r\n  });\r\n\r\n  it('should do something', function() {\r\n    expect(!!myProvider).toBe(true);\r\n  });\r\n\r\n});\r\n```\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuigcerber%2Fangular-unit-testing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpuigcerber%2Fangular-unit-testing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpuigcerber%2Fangular-unit-testing/lists"}