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