UNPKG

37.9 kBMarkdownView Raw
1# ng-describe v1.2.4
2
3> Convenient BDD specs for Angular
4
5[![NPM][ng-describe-icon] ][ng-describe-url]
6[![Quality][quality-badge] ][quality-url]
7
8[![Build status][ng-describe-ci-image] ][ng-describe-ci-url]
9[![Coverage Status][ng-describe-coverage-image] ][ng-describe-coverage-url]
10[![Codacy Badge][ng-describe-codacy-image] ][ng-describe-codacy-url]
11[![dependencies][ng-describe-dependencies-image] ][ng-describe-dependencies-url]
12[![devdependencies][ng-describe-devdependencies-image] ][ng-describe-devdependencies-url]
13
14Tested against angular v1.2, v1.3 and v1.4,
15dependent projects tested using [dont-break][dont-break] - [![Circle CI] [circle-icon] ][circle-url].
16
17Read [Unit testing AngularJS using ng-describe](http://glebbahmutov.com/blog/1-2-3-tested/) tutorial,
18look through [Unit testing](http://slides.com/bahmutov/ng-describe) slides.
19
20Join [Kensho](https://kensho.com/#/careers) and change the way financial industry analyzes information.
21We love the open source and use the bleeding edge technology stack.
22
23[ng-describe-icon]: https://nodei.co/npm/ng-describe.png?downloads=true
24[ng-describe-url]: https://npmjs.org/package/ng-describe
25[ng-describe-ci-image]: https://travis-ci.org/kensho/ng-describe.png?branch=master
26[ng-describe-ci-url]: https://travis-ci.org/kensho/ng-describe
27[ng-describe-coverage-image]: https://coveralls.io/repos/kensho/ng-describe/badge.png
28[ng-describe-coverage-url]: https://coveralls.io/r/kensho/ng-describe
29[ng-describe-dependencies-image]: https://david-dm.org/kensho/ng-describe.png
30[ng-describe-dependencies-url]: https://david-dm.org/kensho/ng-describe
31[ng-describe-devdependencies-image]: https://david-dm.org/kensho/ng-describe/dev-status.png
32[ng-describe-devdependencies-url]: https://david-dm.org/kensho/ng-describe#info=devDependencies
33[ng-describe-codacy-image]: https://www.codacy.com/project/badge/25cb5d1410c7497cb057d887d1f3ea23
34[ng-describe-codacy-url]: https://www.codacy.com/public/kensho/ng-describe.git
35
36[quality-badge]: http://npm.packagequality.com/badge/ng-describe.png
37[quality-url]: http://packagequality.com/#?package=ng-describe
38
39[circle-icon]: https://circleci.com/gh/kensho/ng-describe.svg?style=svg
40[circle-url]: https://circleci.com/gh/kensho/ng-describe
41[dont-break]: https://github.com/bahmutov/dont-break
42
43
44
45* [Intro](#intro)
46* [Install](#install)
47* [API](#api)
48 * [Primary options](#primary-options)
49 * [Secondary options](#secondary-options)
50* [Examples](#examples)
51 * [Test value provided by a module](#test-value-provided-by-a-module)
52 * [Test a filter](#test-a-filter)
53 * [Test a service](#test-a-service)
54 * [Test controller and scope](#test-controller-and-scope)
55 * [Test directive](#test-directive)
56 * [Test controllerAs syntax](#test-controlleras-syntax)
57 * [Test controller instance in custom directive](#test-controller-instance-in-custom-directive)
58 * [Test 2 way binding](#test-2-way-binding)
59 * [beforeEach and afterEach](#beforeeach-and-aftereach)
60 * [Mocking](#mocking)
61 * [Mock value provided by a module](#mock-value-provided-by-a-module)
62 * [Angular services inside mocks](#angular-services-inside-mocks)
63 * [Mock $http.get](#mock-httpget)
64 * [Mock http responses](#mock-http-responses)
65 * [Spying](#spying)
66 * [Spy on injected methods](#spy-on-injected-methods)
67 * [Spy on injected function](#spy-on-injected-function)
68 * [Spy on 3rd party service injected some place else](#spy-on-3rd-party-service-injected-some-place-else)
69 * [Spy on mocked service](#spy-on-mocked-service)
70 * [Configure module](#configure-module)
71 * [Helpful failure messages](#helpful-failure-messages)
72* [Development](#development)
73* [License](#license)
74
75
76## Intro
77
78Unit testing and mocking AngularJs requires a lot of boilerplate code:
79```js
80describe('typical test', function () {
81 var $rootScope, foo;
82 beforeEach(function () {
83 angular.mock.module('A');
84 // other modules
85 });
86 beforeEach(inject(function (_$rootScope_, _foo_) {
87 $rootScope = _$rootScope_;
88 foo = _foo_;
89 }));
90 it('finally a test', function () {
91 $rootScope.$apply(); // for example
92 expect(foo).toEqual('bar');
93 });
94});
95```
96
97ng-describe makes testing simple modules a breeze.
98Just list which modules you would like to load, which values / services / etc.
99you would like to inject and then start testing. Same test as above using ng-describe
100is much shorter and clearer:
101```js
102ngDescribe({
103 modules: 'A',
104 inject: ['$rootScope', 'foo'],
105 tests: function (deps) {
106 it('finally a test', function () {
107 deps.$rootScope.$apply();
108 expect(deps.foo).toEqual('bar');
109 });
110 });
111});
112```
113ng-describe can inject dependencies, mock modules, set configs, create controllers, scopes, and
114even html fragments. For more details, continue reading. We also showed this library at AngularJS NYC
115meetup, the slides are at [slides.com/bahmutov/ng-describe](http://slides.com/bahmutov/ng-describe).
116
117
118## Install
119
120`npm install ng-describe --save-dev`
121
122Load ng-describe.js after angular and angular-mocks but before your specs, for example in Karma conf file.
123
124```js
125// karma.conf.js
126files: [
127 'node_modules/angular/angular.js',
128 'node_modules/angular-mocks/angular-mocks.js',
129 'node_modules/ng-describe/dist/ng-describe.js',
130 '<your source.js>',
131 '<your specs.js>'
132],
133```
134
135File `dist/ng-describe.js` includes es5-shim and other dependencies needed by
136the `ngDescribe` function.
137
138
139## API
140
141ng-describe provides a single function `ngDescribe` that takes an options object.
142
143```js
144ngDescribe({
145 // your options
146});
147```
148
149You do not have to specify every option, there are reasonable defaults. We also tried to make
150the API [user-friendly](http://glebbahmutov.com/blog/user-friendly-api/).
151
152`ngDescribe` returns itself, so you can chain multiple sets of specs easily
153
154```js
155ngDescribe({
156 name: 'first suite'
157 ...
158})({
159 name: 'second suite'
160 ...
161});
162```
163
164### Primary options
165
166**name** - a string name for the spec, similar to BDD `describe(name, ...)`
167
168**modules** - list of modules to inject
169
170```js
171angular.module('A', []);
172angular.module('B', []);
173ngDescribe({
174 name: 'modules example',
175 modules: ['A', 'B']
176});
177```
178
179If you have a single module to inject, you can just use a string name without Array notation
180
181```js
182ngDescribe({
183 name: 'single module',
184 modules: 'A'
185});
186```
187
188**inject** - list of dependencies to inject into unit tests. A single dependency can be just a string
189without Array notation. All dependencies will be exposed as properties of the `deps` argument to the
190tests callback
191
192```js
193angular.module('A', []).value('foo', 42);
194ngDescribe({
195 name: 'inject example',
196 modules: 'A',
197 inject: ['foo', '$timeout'],
198 tests: function (deps) {
199 it('has foo', function () {
200 expect(deps.foo).toEqual(42);
201 });
202 it('has timeout service', function () {
203 expect(typeof deps.$timeout).toEqual('function');
204 });
205 }
206});
207```
208
209**tests** - callback function that contains actual specs. Think of this as equivalent to `describe` with
210all necessary Angular dependencies taken care of.
211
212```js
213ngDescribe({
214 inject: ['$q', '$rootScope'],
215 tests: function (deps) {
216 it('injects $q', function () {
217 expect(typeof deps.$q).toEqual('function');
218 });
219 it('can be resolved', function () {
220 deps.$q.when(42).then(function (value) {
221 expect(value).toEqual(42);
222 });
223 // move promises along
224 deps.$rootScope.$digest();
225 });
226 }
227});
228```
229
230**mocks** - top level mocks to be substituted into the tests.
231The mocks override *any* injected dependencies among modules.
232
233```js
234ngDescribe({
235 mocks: {
236 // each module to mock by name
237 moduleName1: {
238 // each dependency from moduleName1 to mock
239 dependencyName1: mockValue1,
240 dependencyName2: mockValue2
241 // the rest of moduleName1 is unchanged
242 },
243 moduleName2: {
244 // dependencies to mock in moduleName2
245 }
246 }
247});
248```
249
250For more information see examples below.
251
252**controllers** - list of controllers by name that should be injected. Each controller
253is created with a new `$rootScope` instance.
254
255**NOTE: For each created controller, its SCOPE instance will be in the dependencies object.**
256
257```js
258angular.module('D', [])
259 .controller('dController', function ($scope) {
260 $scope.foo = 'foo';
261 });
262ngDescribe({
263 modules: 'D',
264 controllers: 'dController',
265 tests: function (deps) {
266 it('is a scope for controller', function () {
267 expect(typeof deps.dController).toEqual('object');
268 // deps.dController is the $scope object injected into dController
269 expect(deps.dController.foo).toEqual('foo');
270 });
271 }
272});
273```
274
275**element** - HTML fragment string for testing custom directives and DOM updates.
276
277```js
278ngDescribe({
279 element: '<my-foo bar="baz"></my-foo>'
280});
281```
282
283The compiled `angular.element` will be injected into the dependencies object under `element` property.
284See examples below for more information. The compilation will create a new scope object too.
285
286**parentScope** - when creating HTML fragment, copies properties from this object into the
287scope. The returned dependencies object will have `deps.parentScope` that is the new scope.
288
289```js
290// myFoo directive uses isolate scope for example
291ngDescribe({
292 element: '<my-foo bar="baz"></my-foo>',
293 parentScope: {
294 baz: 42
295 },
296 tests: function (deps) {
297 it('baz -> bar', function () {
298 deps.parentScope.baz = 100;
299 deps.$rootScope.$apply();
300 expect(deps.element.isolateScope().bar).toEqual(100);
301 });
302 }
303});
304```
305
306See "2 way binding" example below.
307
308**configs** - object with modules that have provider that can be used to inject
309run time settings.
310See *Update 1* in
311[Inject valid constants into Angular](http://glebbahmutov.com/blog/inject-valid-constants-into-angular/)
312blog post and examples below.
313
314### Secondary options
315
316**verbose** - flag to print debug messages during execution
317
318**only** - flag to run this set of tests and skip the rest. Equivalent to
319[ddescribe or describe.only](http://glebbahmutov.com/blog/focus-on-karma-test/).
320
321```js
322ngDescribe({
323 name: 'run this module only',
324 only: true
325});
326```
327
328**skip** - flag to skip this group of specs. Equivalent to `xdescribe` or `describe.skip`.
329Could be a string message explaining the reason for skipping the spec.
330
331**exposeApi** - expose low-level ngDescribe methods
332
333The `tests` callback will get the second argument, which is an object with the following methods
334
335 {
336 setupElement: function (elementHtml),
337 setupControllers: function (controllerNames)
338 }
339
340You can use `setupElement` to control when to create the element.
341For example, instead of creating element right away, expose element factory so that you can create
342an element *after* running a `beforeEach` block. Useful for setting up mock backend before creating
343an element.
344
345```js
346ngDescribe({
347 exposeApi: true,
348 inject: '$httpBackend',
349 // no element option
350 tests: function (deps, describeApi) {
351 beforeEach(function () {
352 deps.$httpBackend
353 .expectGET('/api/foo/bar').respond(500);
354 });
355 beforeEach(function () {
356 // now create an element ourselves
357 describeApi.setupElement('<study-flags />');
358 });
359 it('created an element', function () {
360 la(check.has(deps.element));
361 });
362 });
363});
364```
365
366See the spec in [test/expose-spec.js](test/expose-spec.js)
367
368Or you can use `setupControllers` to create controller objects AFTER setting up your spies.
369
370```js
371angular.module('BroadcastController', [])
372 .controller('broadcastController', function broadcastController($rootScope) {
373 $rootScope.$broadcast('foo');
374 });
375```
376
377We need to listen for the `foo` broadcast inside a unit test before creating the controller.
378If we let `ngDescribe` create the "broadcastController" it will be too late. Instead we
379can tell the `ngDescribe` to expose the low-level api and then we create the controllers when
380we are ready
381
382```js
383ngDescribe({
384 name: 'spy on controller init',
385 modules: 'BroadcastController',
386 inject: '$rootScope',
387 exposeApi: true,
388 tests: function (deps, describeApi) {
389 it('can catch the broadcast in controller init', function (done) {
390 var heardFoo;
391 deps.$rootScope.$on('foo', function () {
392 heardFoo = true;
393 done();
394 });
395 describeApi.setupControllers('broadcastController');
396 });
397 }
398});
399```
400
401See the spec in [test/controller-init-spec.js](test/controller-init-spec.js)
402
403**http** - shortcut for specifying mock HTTP responses,
404built on top of [$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).
405Each GET request will be mapped to `$httpBackend.whenGET` for example. You can provide
406data, response code + data pair or custom function to return something using custom logic.
407If you use `http` property, then the injected dependencies will have `http` object that
408you can flush (it is really `$httpBackend` object).
409
410```js
411ngDescribe({
412 inject: '$http', // for making test calls
413 http: {
414 get: {
415 '/my/url': 42, // status 200, data 42
416 '/my/other/url': [202, 42], // status 202, data 42,
417 '/my/smart/url': function (method, url, data, headers) {
418 return [500, 'something is wrong'];
419 } // status 500, data "something is wrong"
420 },
421 post: {
422 // same format as GET
423 }
424 },
425 tests: function (deps) {
426 it('responds', function (done) {
427 deps.$http.get('/my/other/url')
428 .then(function (response) {
429 // response.status = 202
430 // response.data = 42
431 done();
432 });
433 http.flush();
434 });
435 }
436});
437```
438All standard methods should be supported (`get`, `head`, `post`, `put`, `delete`, `jsonp` and `patch`).
439
440Each of the methods can return a function that returns an configuration object, see [mock http](#mock-http).
441
442**step** - shortcut for running the digest cycle and mock http flush
443
444```js
445tests: function (deps) {
446 it('runs the digest cycle', function (done) {
447 $q.when(42).finally(done);
448 deps.step();
449 // same as deps.$rootScope.$digest();
450 });
451}
452```
453
454Also flushes the mock http backend
455
456```js
457http: {}
458tests: function (deps) {
459 it('returns expected result', function (done) {
460 deps.$http.get(...)
461 .then(...)
462 .finally(done);
463 deps.step();
464 // same as deps.http.flush();
465 });
466}
467```
468
469
470## Examples
471
472Some examples use Jasmine matchers, others use `la` assertion from
473[lazy-ass](https://github.com/bahmutov/lazy-ass) library and *done* callback argument
474from [Mocha](http://visionmedia.github.io/mocha/) testing framework.
475
476Also, note that the dependencies object is filled **only** inside the unit test callbacks `it` and
477setup helpers `beforeEach` and `afterEach`
478
479```js
480ngDescribe({
481 inject: 'foo',
482 tests: function (deps) {
483 // deps is an empty object here
484 beforeEach(function () {
485 // deps object has 'foo'
486 });
487 // deps is an empty object here
488 it(function () {
489 // deps object has 'foo'
490 });
491 // deps is an empty object here
492 afterEach(function () {
493 // deps object has 'foo'
494 });
495 }
496});
497```
498
499### Test value provided by a module
500
501```js
502// A.js
503angular.module('A', [])
504 .value('foo', 'bar');
505// A-spec.js
506ngDescribe({
507 name: 'test value',
508 modules: 'A',
509 inject: 'foo',
510 tests: function (deps) {
511 // deps object has every injected dependency as a property
512 it('has correct value foo', function () {
513 expect(deps.foo).toEqual('bar');
514 });
515 }
516});
517```
518
519### Test a filter
520
521We can easily test a built-in or custom filter function
522
523```js
524ngDescribe({
525 name: 'built-in filter',
526 inject: '$filter',
527 tests: function (deps) {
528 it('can convert to lowercase', function () {
529 var lowercase = deps.$filter('lowercase');
530 la(lowercase('Foo') === 'foo');
531 });
532 }
533});
534```
535
536### Test a service
537
538We can inject a service to test using the same approach. You can even use multiple specs inside `tests` callback.
539
540```js
541// B.js
542angular.module('B', ['A'])
543 .service('addFoo', function (foo) {
544 return function (str) {
545 return str + foo;
546 };
547 });
548// B-spec.js
549ngDescribe({
550 name: 'service tests',
551 modules: 'B',
552 inject: 'addFoo',
553 tests: function (deps) {
554 it('is a function', function () {
555 expect(typeof deps.addFoo).toEqual('function');
556 });
557 it('appends value of foo to any string', function () {
558 var result = deps.addFoo('x');
559 expect(result).toEqual('xbar');
560 });
561 }
562});
563```
564
565### Test controller and scope
566
567We can easily create instances of controller functions and scope objects.
568In this example we also inject `$timeout` service to speed up delayed actions
569(see [Testing Angular async stuff](http://glebbahmutov.com/blog/testing-angular-async-stuff/)).
570
571```js
572angular.module('S', [])
573 .controller('sample', function ($timeout, $scope) {
574 $scope.foo = 'foo';
575 $scope.update = function () {
576 $timeout(function () {
577 $scope.foo = 'bar';
578 }, 1000);
579 };
580 });
581ngDescribe({
582 name: 'timeout in controller',
583 modules: 'S',
584 // inject $timeout so we can flush the timeout queue
585 inject: ['$timeout'],
586 controllers: 'sample',
587 tests: function (deps) {
588 // deps.sample = $scope object injected into sample controller
589 it('has initial values', function () {
590 la(deps.sample.foo === 'foo');
591 });
592 it('updates after timeout', function () {
593 deps.sample.update();
594 deps.$timeout.flush();
595 la(deps.sample.foo === 'bar');
596 });
597 }
598});
599```
600
601### Test directive
602
603```js
604angular.module('MyFoo', [])
605 .directive('myFoo', function () {
606 return {
607 restrict: 'E',
608 replace: true,
609 template: '<span>{{ bar }}</span>'
610 };
611 });
612ngDescribe({
613 name: 'MyFoo directive',
614 modules: 'MyFoo',
615 element: '<my-foo></my-foo>',
616 tests: function (deps) {
617 it('can update DOM using binding', function () {
618 la(check.has(deps, 'element'), 'has compiled element');
619 var scope = deps.element.scope();
620 scope.bar = 'bar';
621 scope.$apply();
622 la(deps.element.html() === 'bar');
623 });
624 }
625});
626```
627
628### Test controllerAs syntax
629
630If you use `controllerAs` syntax without any components (see [Binding to ...][binding] post or
631[Separate ...][separate]), then you can still test it quickly
632
633```js
634angular.module('H', [])
635 .controller('hController', function () {
636 // notice we attach properties to the instance, not to the $scope
637 this.foo = 'foo';
638 });
639 ngDescribe({
640 module: 'H',
641 element: '<div ng-controller="hController as ctrl">{{ ctrl.foo }}</div>',
642 tests: function (deps) {
643 it('created controller correctly', function () {
644 var compiledHtml = deps.element.html();
645 // 'foo'
646 });
647 it('changes value', function () {
648 var ctrl = deps.element.controller();
649 // { foo: 'foo' }
650 ctrl.foo = 'bar';
651 deps.element.scope().$apply();
652 var compiledHtml = deps.element.html();
653 // 'bar'
654 });
655 }
656 });
657```
658
659[binding]: http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
660[separate]: http://glebbahmutov.com/blog/separate-model-from-view-in-angular/
661
662### Test controller instance in custom directive
663
664If you add methods to the controller inside custom directive, use `controllerAs` syntax to
665expose the controller instance.
666
667```js
668angular.module('C', [])
669 .directive('cDirective', function () {
670 return {
671 controllerAs: 'ctrl', // puts controller instance onto scope as ctrl
672 controller: function ($scope) {
673 $scope.foo = 'foo';
674 this.foo = function getFoo() {
675 return $scope.foo;
676 };
677 }
678 };
679 });
680ngDescribe({
681 name: 'controller for directive instance',
682 modules: 'C',
683 element: '<c-directive></c-directive>',
684 tests: function (deps) {
685 it('has controller', function () {
686 var scope = deps.element.scope(); // grabs scope
687 var controller = scope.ctrl; // grabs controller instance
688 la(typeof controller.foo === 'function');
689 la(controller.foo() === 'foo');
690 scope.foo = 'bar';
691 la(controller.foo() === 'bar');
692 });
693 }
694});
695```
696
697### Test 2 way binding
698
699If a directive implements isolate scope, we can configure parent scope separately.
700
701```js
702angular.module('IsolateFoo', [])
703 .directive('aFoo', function () {
704 return {
705 restrict: 'E',
706 replace: true,
707 scope: {
708 bar: '='
709 },
710 template: '<span>{{ bar }}</span>'
711 };
712 });
713```
714
715We can use `element` together with `parentScope` property to set initial values.
716
717```js
718ngDescribe({
719 modules: 'IsolateFoo',
720 element: '<a-foo bar="x"></a-foo>',
721 parentScope: {
722 x: 'initial'
723 },
724 tests: function (deps) {
725 it('has correct initial value', function () {
726 var scope = deps.element.isolateScope();
727 expect(scope.bar).toEqual('initial');
728 });
729 }
730});
731```
732
733We can change parent's values to observe propagation into the directive
734
735```js
736// same setup
737it('updates isolate scope', function () {
738 deps.parentScope.x = 42;
739 deps.$rootScope.$apply();
740 var scope = deps.element.isolateScope();
741 expect(scope.bar).toEqual(42);
742});
743```
744
745### beforeEach and afterEach
746
747You can use multiple `beforeEach` and `afterEach` inside `tests` function.
748
749```js
750ngDescribe({
751 name: 'before and after example',
752 modules: ['A'],
753 inject: ['foo'],
754 tests: function (deps) {
755 var localFoo;
756 beforeEach(function () {
757 // dependencies are already injected
758 la(deps.foo === 'bar');
759 localFoo = deps.foo;
760 });
761 it('has correct value foo', function () {
762 la(localFoo === 'bar');
763 });
764 afterEach(function () {
765 la(localFoo === 'bar');
766 // dependencies are still available
767 la(deps.foo === 'bar');
768 });
769 }
770});
771```
772
773This could be useful for setting up additional mocks, like `$httpBackend`.
774
775```js
776angular.module('apiCaller', [])
777 .service('getIt', function ($http) {
778 return function () {
779 return $http.get('/my/url');
780 };
781 });
782ngDescribe({
783 name: 'http mock backend example',
784 modules: ['apiCaller'],
785 inject: ['getIt', '$httpBackend'],
786 tests: function (deps) {
787 beforeEach(function () {
788 deps.$httpBackend.expectGET('/my/url').respond(200, 42);
789 });
790 it('returns result from server', function (done) {
791 deps.getIt().then(function (response) {
792 la(response && response.status === 200);
793 la(response.data === 42);
794 done();
795 });
796 deps.$httpBackend.flush();
797 });
798 afterEach(function () {
799 deps.$httpBackend.verifyNoOutstandingRequest();
800 deps.$httpBackend.verifyNoOutstandingExpectation();
801 });
802 }
803});
804```
805
806**Note** if you use `beforeEach` block with `element`, the `beforeEach` runs *before* the element
807is created. This gives you a chance to setup mocks before running the element and possibly making calls.
808If you really want to control when an element is created use `exposeApi` option
809(see [Secondary options](#secondary-options)).
810
811### Mocking
812
813#### Mock value provided by a module
814
815Often during testing we need to mock something provided by a module, even if it is
816passed via dependency injection. ng-describe makes it very simple. List all modules with values
817to be mocked in `mocks` object property.
818
819```js
820// C.js
821angular.module('C', ['A'])
822 .service('getFoo', function (foo) {
823 // foo is provided by module A
824 return function getFoo() {
825 return foo;
826 };
827 });
828// C-spec.js
829ngDescribe({
830 name: 'test C with mocking top level',
831 modules: ['C'],
832 inject: ['getFoo'],
833 mocks: {
834 // replace C.getFoo with mock function that returns 11
835 C: {
836 getFoo: function () {
837 return 11;
838 }
839 }
840 },
841 verbose: false,
842 tests: function (deps) {
843 it('has mock injected value', function () {
844 var result = deps.getFoo();
845 la(result === 11, 'we got back mock value', result);
846 });
847 }
848});
849```
850
851Remember when making mocks, it is always `module name : provider name : mocked property name`
852
853```js
854mocks: {
855 'module name': {
856 'mocked provider name': {
857 'mocked value name'
858 }
859 }
860}
861```
862
863Note: the mocked values are injected using `$provider.constant` call to be able to override both
864values and constants
865
866```js
867angular.module('A10', [])
868 .constant('foo', 'bar');
869ngDescribe({
870 modules: 'A10',
871 mock: {
872 A10: {
873 foo: 42
874 }
875 },
876 inject: 'foo',
877 tests: function (deps) {
878 it('has correct constant foo', function () {
879 expect(deps.foo).toEqual(42);
880 });
881 }
882});
883```
884
885You can even mock part of the module itself and use mock value in other parts via injection
886
887```js
888angular.module('LargeModule', [])
889 .constant('foo', 'foo')
890 .service('getFoo', function (foo) {
891 return function getFoo() {
892 return foo;
893 };
894 });
895ngDescribe({
896 name: 'mocking part of the module itself',
897 modules: 'LargeModule',
898 inject: 'getFoo',
899 mock: {
900 LargeModule: {
901 foo: 'bar'
902 }
903 },
904 tests: function (deps) {
905 it('service injects mock value', function () {
906 la(deps.getFoo() === 'bar', 'returns mock value');
907 });
908 }
909});
910```
911
912#### Angular services inside mocks
913
914You can use other injected dependencies inside mocked functions, using
915injected values and free parameters.
916
917```js
918ngDescribe({
919 inject: ['getFoo', '$rootScope'],
920 mocks: {
921 C: {
922 // use angular $q service in the mock function
923 // argument "value" remains free
924 getFoo: function ($q, value) {
925 return $q.when(value);
926 }
927 }
928 },
929 tests: function (deps) {
930 it('injected $q into mock', function (done) {
931 deps.getFoo('foo').then(function (result) {
932 expect(result).toEqual('foo');
933 done();
934 });
935 deps.$rootScope.$apply(); // resolve promise
936 });
937 }
938});
939```
940
941#### Mock $http.get
942
943Often we need some dummy response from `$http.get` method. We can use mock `httpBackend`
944or mock the `$http` object. For example to always return mock value when making any GET request,
945we can use
946
947```js
948mocks: {
949 ng: {
950 $http: {
951 get: function ($q, url) {
952 // inspect url if needed
953 return $q.when({
954 data: {
955 life: 42
956 }
957 });
958 }
959 }
960 }
961}
962```
963
964`$http` service returns a promise that resolves with a *response* object. The actual result to send
965is placed into the `data` property, as I show here.
966
967#### Mock http responses
968
969You can use a shortcut to define mock HTTP responses via `$httpBackend` module. For example,
970you can define static responses.
971
972```js
973ngDescribe({
974 http: {
975 get: {
976 '/some/url': 42,
977 '/some/other/url': [500, 'something went wrong']
978 },
979 post: {
980 // you can use custom functions too
981 '/some/post/url': function (method, url, data, headers) {
982 return [200, 'ok'];
983 }
984 }
985 }
986});
987```
988All HTTP methods are supported (`get`, `post`, `delete`, `put`, etc.).
989
990You can also get a function that would return a config object.
991
992```js
993var mockGetApi = {
994 '/some/url': 42
995};
996mockGetApi['/some/other/url'] = [500, 'not ok'];
997ngDescribe({
998 http: {
999 get: mockGetApi
1000 }
1001});
1002```
1003
1004You can use `deps.http.flush()` to move the http responses along.
1005
1006You can return the entire http mock object from a function, or combine objects with functions.
1007
1008```js
1009function constructMockApi() {
1010 return {
1011 get: function () {
1012 return { '/my/url': 42 };
1013 },
1014 post: {
1015 '/my/other/url': [200, 'nice']
1016 }
1017 };
1018}
1019ngDescribe({
1020 http: constructMockApi,
1021 test: function (deps) {
1022 ...
1023 }
1024});
1025```
1026
1027You can use exact query arguments too
1028
1029```js
1030http: {
1031 get: {
1032 '/foo/bar?search=value': 42,
1033 '/foo/bar?search=value&something=else': 'foo'
1034 }
1035}
1036// $http.get('/foo/bar?search=value') will resolve with value 42
1037// $http.get('/foo/bar?search=value&something=else') will resolve with value 'foo'
1038```
1039
1040or you can build the query string automatically by passing `params` property in the request config
1041objet
1042
1043```js
1044http: {
1045 get: {
1046 '/foo/bar?search=value&something=else': 'foo'
1047 }
1048}
1049// inside the unit test
1050var config = {
1051 params: {
1052 search: 'value',
1053 something: 'else'
1054 }
1055};
1056$http.get('/foo/bar', config).then(function (response) {
1057 // response.data = 'foo'
1058});
1059```
1060
1061**note** the `http` mocks are defined using `$httpBack.when(method, ...)` calls,
1062which are looser than `$httpBackend.expect(method, ...)`,
1063see [ngMock/$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).
1064
1065### Spying
1066
1067#### Spy on injected methods
1068
1069One can quickly spy on injected services (or other methods) using [sinon.js](http://sinonjs.org/)
1070similarly to [spying on the regular JavaScript methods](http://glebbahmutov.com/blog/spying-on-methods/).
1071
1072* Include a browser-compatible combined [sinon.js build](http://sinonjs.org/releases/sinon-1.12.1.js)
1073into the list of loaded Karma files.
1074* Setup spy in the `beforeEach` function. Since every injected service is a method on the `deps`
1075object, the setup is a single command.
1076* Restore the original method in `afterEach` function.
1077
1078```js
1079// source code
1080angular.module('Tweets', [])
1081 .service('getTweets', function () {
1082 return function getTweets(username) {
1083 console.log('returning # of tweets for', username);
1084 return 42;
1085 };
1086 });
1087```
1088
1089```js
1090// spec
1091ngDescribe({
1092 name: 'spying on Tweets getTweets service',
1093 modules: 'Tweets',
1094 inject: 'getTweets',
1095 tests: function (deps) {
1096 beforeEach(function () {
1097 sinon.spy(deps, 'getTweets');
1098 });
1099 afterEach(function () {
1100 deps.getTweets.restore();
1101 });
1102 it('calls getTweets service', function () {
1103 var n = deps.getTweets('foo');
1104 la(n === 42, 'resolved with correct value');
1105 la(deps.getTweets.called, 'getTweets was called (spied using sinon)');
1106 la(deps.getTweets.firstCall.calledWith('foo'));
1107 });
1108 }
1109});
1110```
1111
1112#### Spy on injected function
1113
1114You can inject a function, but use a [Sinon spy](http://sinonjs.org/docs/#spies) instead
1115of the injected function to get additional information. For example, to spy on the `$filter uppercase`,
1116we can use the following code.
1117
1118```js
1119ngDescribe({
1120 name: 'spying on a filter',
1121 inject: '$filter',
1122 tests: function (deps) {
1123 /*
1124 to spy on a injected filter, need to grab the actual filter function
1125 and then create a spy
1126 */
1127 // _uppercase = angular uppercase $filter
1128 // uppercase = spy on the _uppercase
1129 var _uppercase, uppercase;
1130 beforeEach(function () {
1131 _uppercase = deps.$filter('uppercase');
1132 uppercase = sinon.spy(_uppercase);
1133 });
1134 it('converts string to uppercase', function () {
1135 var result = uppercase('foo');
1136 la(result === 'FOO', 'converted string to uppercase', result);
1137 la(uppercase.calledOnce, 'uppercase was called once');
1138 la(uppercase.calledWith('foo'));
1139 });
1140 }
1141});
1142```
1143
1144#### Spy on 3rd party service injected some place else
1145
1146Let us say you need to verify that the `$interval` service injected in the module under test
1147was called. It is a little verbose to verify from the unit test. We must mock the `$interval`
1148with our function and then call the actual `$interval` from the module `ng` to provide the
1149same functionality.
1150
1151Source code we are trying to unit test
1152
1153```js
1154angular.module('IntervalExample', [])
1155 .service('numbers', function ($interval, $rootScope) {
1156 return function emitNumbers(delay, n) {
1157 var k = 0;
1158 $interval(function () {
1159 $rootScope.$emit('number', k);
1160 k += 1;
1161 }, 100, n);
1162 };
1163 });
1164```
1165
1166In the unit test we will mock `$interval` service for module `IntervalExample`
1167
1168```js
1169// unit test start
1170var intervalCalled;
1171ngDescribe({
1172 name: 'spying on $interval',
1173 module: 'IntervalExample',
1174 inject: ['numbers', '$rootScope'],
1175 verbose: false,
1176 only: false,
1177 mocks: {
1178 IntervalExample: {
1179 $interval: function mockInterval(fn, delay, n) {
1180 var injector = angular.injector(['ng']);
1181 var $interval = injector.get('$interval');
1182 intervalCalled = true;
1183 return $interval(fn, delay, n);
1184 }
1185 }
1186 },
1187 tests: function (deps) {
1188 // unit test goes here
1189 }
1190});
1191```
1192
1193A unit test just calls the `numbers` function and then checks the variable `intervalCalled`
1194
1195```js
1196it('emits 3 numbers', function (done) {
1197 deps.$rootScope.$on('number', function (event, k) {
1198 if (k === 2) {
1199 done();
1200 }
1201 });
1202 // emit 3 numbers with 100ms interval
1203 deps.numbers(100, 3);
1204 la(intervalCalled, 'the $interval was called somewhere');
1205});
1206```
1207
1208You can see the unit test in file [test/spying-on-interval-spec.js](test/spying-on-interval-spec.js).
1209
1210#### Spy on mocked service
1211
1212If we mock an injected service, we can still spy on it, just like as if we were spying on the
1213regular service. For example, let us take the same method as above and mock it.
1214
1215```js
1216angular.module('Tweets', [])
1217 .service('getTweets', function () {
1218 return function getTweets(username) {
1219 return 42;
1220 };
1221 });
1222```
1223
1224The mock will return a different number.
1225
1226```js
1227ngDescribe({
1228 name: 'spying on mock methods',
1229 inject: 'getTweets',
1230 mocks: {
1231 Tweets: {
1232 getTweets: function (username) {
1233 return 1000;
1234 }
1235 }
1236 },
1237 tests: function (deps) {
1238 beforeEach(function () {
1239 sinon.spy(deps, 'getTweets');
1240 });
1241 afterEach(function () {
1242 deps.getTweets.restore();
1243 });
1244 it('calls mocked getTweets service', function () {
1245 var n = deps.getTweets('bar');
1246 la(n === 1000, 'resolved with correct value from the mock service');
1247 la(deps.getTweets.called,
1248 'mock service getTweets was called (spied using sinon)');
1249 la(deps.getTweets.firstCall.calledWith('bar'),
1250 'mock service getTweets was called with expected argument');
1251 });
1252 }
1253});
1254```
1255
1256### Configure module
1257
1258If you use a separate module with namesake provider to pass configuration into the modules
1259(see [Inject valid constants into Angular](http://glebbahmutov.com/blog/inject-valid-constants-into-angular/)),
1260you can easily configure these modules.
1261
1262```js
1263angular.module('App', ['AppConfig'])
1264 .service('foo', function (AppConfig) {
1265 return function foo() {
1266 return GConfig.bar;
1267 };
1268 });
1269// config module has provider with same name
1270angular.module('AppConfig', [])
1271 .provider('AppConfig', function () {
1272 var config = {};
1273 return {
1274 set: function (settings) {
1275 config = settings;
1276 },
1277 $get: function () {
1278 return config;
1279 }
1280 };
1281 });
1282// spec file
1283ngDescribe({
1284 name: 'config module example',
1285 modules: 'App',
1286 inject: 'foo',
1287 configs: {
1288 // every config module will be loaded automatically
1289 AppConfig: {
1290 bar: 'boo!'
1291 }
1292 },
1293 tests: function (deps) {
1294 it('foo has configured bar value', function () {
1295 expect(deps.foo()).toEqual('boo!');
1296 });
1297 }
1298});
1299```
1300
1301You can configure multiple modules at the same time. Note that during the configuration
1302Angular is yet to be loaded. Thus you cannot use Angular services inside the configuration blocks.
1303
1304### Helpful failure messages
1305
1306ng-describe works inside [helpDescribe function](https://github.com/bahmutov/lazy-ass-helpful#lazy-ass-helpful-bdd),
1307producing meaningful error messages on failure (if you use [lazy assertions](https://github.com/bahmutov/lazy-ass)).
1308
1309```js
1310helpDescribe('ngDescribe inside helpful', function () {
1311 ngDescribe({
1312 name: 'example',
1313 tests: function () {
1314 it('gives helpful error message', function () {
1315 var foo = 2, bar = 3;
1316 la(foo + bar === 4); // wrong on purpose
1317 });
1318 }
1319 });
1320});
1321```
1322when this test fails, it generates meaningful message with all relevant information: the expression
1323that fails `foo + bar === 4` and runtime values of `foo` and `bar`.
1324
1325 PhantomJS 1.9.7 (Mac OS X)
1326 ட ngDescribe inside helpful
1327 ட example
1328 ட ✘ gives helpful error message FAILED
1329 Error: condition [foo + bar === 4] foo: 2 bar: 3
1330 at lazyAss (/ng-describe/node_modules/lazy-ass/index.js:57)
1331 PhantomJS 1.9.7 (Mac OS X): Executed 37 of 38 (1 FAILED) (skipped 1) (0.053 secs / 0.002 secs)
1332
1333
1334## Development
1335
1336To build the README document, run unit tests and linter
1337
1338 npm run build
1339
1340To run all unit tests (against different Angular versions)
1341
1342 npm test
1343
1344To keep a watch and rerun build + lint + tests on source file change
1345
1346 npm run watch
1347
1348For now, all source is in a single `ng-describe.js` file, while the documentation
1349is generated from Markdown files in the `docs` folder
1350
1351To just run karma unit tests via Grunt plugin
1352
1353 npm run karma
1354
1355If you have Karma runner installed globally you can run all the unit tests yourself ones
1356
1357 karma start --single-run=true test/karma.conf.js
1358
1359
1360## License
1361
1362Author: Kensho &copy; 2014
1363
1364* [@kensho](https://twitter.com/kensho)
1365* [kensho.com](http://kensho.com)
1366
1367Support: if you find any problems with this library,
1368[open issue](https://github.com/kensho/ng-describe/issues) on Github
1369
1370
1371The MIT License (MIT)
1372
1373Copyright (c) 2014 Kensho
1374
1375Permission is hereby granted, free of charge, to any person obtaining a copy of
1376this software and associated documentation files (the "Software"), to deal in
1377the Software without restriction, including without limitation the rights to
1378use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
1379the Software, and to permit persons to whom the Software is furnished to do so,
1380subject to the following conditions:
1381
1382The above copyright notice and this permission notice shall be included in all
1383copies or substantial portions of the Software.
1384
1385THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1386IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
1387FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
1388COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
1389IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
1390CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1391
1392
1393