UNPKG

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