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 |
|
14 | Tested against angular v1.2, v1.3 and v1.4,
|
15 | dependent projects tested using [dont-break][dont-break] - [![Circle CI] [circle-icon] ][circle-url].
|
16 |
|
17 | Read [Unit testing AngularJS using ng-describe](http://glebbahmutov.com/blog/1-2-3-tested/) tutorial,
|
18 | look through [Unit testing](http://slides.com/bahmutov/ng-describe) slides.
|
19 |
|
20 | Join [Kensho](https://kensho.com/#/careers) and change the way financial industry analyzes information.
|
21 | We 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 |
|
78 | Unit testing and mocking AngularJs requires a lot of boilerplate code:
|
79 | ```js
|
80 | describe('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 |
|
97 | ng-describe makes testing simple modules a breeze.
|
98 | Just list which modules you would like to load, which values / services / etc.
|
99 | you would like to inject and then start testing. Same test as above using ng-describe
|
100 | is much shorter and clearer:
|
101 | ```js
|
102 | ngDescribe({
|
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 | ```
|
113 | ng-describe can inject dependencies, mock modules, set configs, create controllers, scopes, and
|
114 | even html fragments. For more details, continue reading. We also showed this library at AngularJS NYC
|
115 | meetup, 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 |
|
122 | Load 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
|
126 | files: [
|
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 |
|
135 | File `dist/ng-describe.js` includes es5-shim and other dependencies needed by
|
136 | the `ngDescribe` function.
|
137 |
|
138 |
|
139 | ## API
|
140 |
|
141 | ng-describe provides a single function `ngDescribe` that takes an options object.
|
142 |
|
143 | ```js
|
144 | ngDescribe({
|
145 | // your options
|
146 | });
|
147 | ```
|
148 |
|
149 | You do not have to specify every option, there are reasonable defaults. We also tried to make
|
150 | the 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
|
155 | ngDescribe({
|
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
|
171 | angular.module('A', []);
|
172 | angular.module('B', []);
|
173 | ngDescribe({
|
174 | name: 'modules example',
|
175 | modules: ['A', 'B']
|
176 | });
|
177 | ```
|
178 |
|
179 | If you have a single module to inject, you can just use a string name without Array notation
|
180 |
|
181 | ```js
|
182 | ngDescribe({
|
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
|
189 | without Array notation. All dependencies will be exposed as properties of the `deps` argument to the
|
190 | tests callback
|
191 |
|
192 | ```js
|
193 | angular.module('A', []).value('foo', 42);
|
194 | ngDescribe({
|
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
|
210 | all necessary Angular dependencies taken care of.
|
211 |
|
212 | ```js
|
213 | ngDescribe({
|
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.
|
231 | The mocks override *any* injected dependencies among modules.
|
232 |
|
233 | ```js
|
234 | ngDescribe({
|
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 |
|
250 | For more information see examples below.
|
251 |
|
252 | **controllers** - list of controllers by name that should be injected. Each controller
|
253 | is 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
|
258 | angular.module('D', [])
|
259 | .controller('dController', function ($scope) {
|
260 | $scope.foo = 'foo';
|
261 | });
|
262 | ngDescribe({
|
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
|
278 | ngDescribe({
|
279 | element: '<my-foo bar="baz"></my-foo>'
|
280 | });
|
281 | ```
|
282 |
|
283 | The compiled `angular.element` will be injected into the dependencies object under `element` property.
|
284 | See 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
|
287 | scope. The returned dependencies object will have `deps.parentScope` that is the new scope.
|
288 |
|
289 | ```js
|
290 | // myFoo directive uses isolate scope for example
|
291 | ngDescribe({
|
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 |
|
306 | See "2 way binding" example below.
|
307 |
|
308 | **configs** - object with modules that have provider that can be used to inject
|
309 | run time settings.
|
310 | See *Update 1* in
|
311 | [Inject valid constants into Angular](http://glebbahmutov.com/blog/inject-valid-constants-into-angular/)
|
312 | blog 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
|
322 | ngDescribe({
|
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`.
|
329 | Could be a string message explaining the reason for skipping the spec.
|
330 |
|
331 | **exposeApi** - expose low-level ngDescribe methods
|
332 |
|
333 | The `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 |
|
340 | You can use `setupElement` to control when to create the element.
|
341 | For example, instead of creating element right away, expose element factory so that you can create
|
342 | an element *after* running a `beforeEach` block. Useful for setting up mock backend before creating
|
343 | an element.
|
344 |
|
345 | ```js
|
346 | ngDescribe({
|
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 |
|
366 | See the spec in [test/expose-spec.js](test/expose-spec.js)
|
367 |
|
368 | Or you can use `setupControllers` to create controller objects AFTER setting up your spies.
|
369 |
|
370 | ```js
|
371 | angular.module('BroadcastController', [])
|
372 | .controller('broadcastController', function broadcastController($rootScope) {
|
373 | $rootScope.$broadcast('foo');
|
374 | });
|
375 | ```
|
376 |
|
377 | We need to listen for the `foo` broadcast inside a unit test before creating the controller.
|
378 | If we let `ngDescribe` create the "broadcastController" it will be too late. Instead we
|
379 | can tell the `ngDescribe` to expose the low-level api and then we create the controllers when
|
380 | we are ready
|
381 |
|
382 | ```js
|
383 | ngDescribe({
|
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 |
|
401 | See the spec in [test/controller-init-spec.js](test/controller-init-spec.js)
|
402 |
|
403 | **http** - shortcut for specifying mock HTTP responses,
|
404 | built on top of [$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).
|
405 | Each GET request will be mapped to `$httpBackend.whenGET` for example. You can provide
|
406 | data, response code + data pair or custom function to return something using custom logic.
|
407 | If you use `http` property, then the injected dependencies will have `http` object that
|
408 | you can flush (it is really `$httpBackend` object).
|
409 |
|
410 | ```js
|
411 | ngDescribe({
|
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 | ```
|
438 | All standard methods should be supported (`get`, `head`, `post`, `put`, `delete`, `jsonp` and `patch`).
|
439 |
|
440 | Each 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
|
445 | tests: 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 |
|
454 | Also flushes the mock http backend
|
455 |
|
456 | ```js
|
457 | http: {}
|
458 | tests: 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 |
|
472 | Some examples use Jasmine matchers, others use `la` assertion from
|
473 | [lazy-ass](https://github.com/bahmutov/lazy-ass) library and *done* callback argument
|
474 | from [Mocha](http://visionmedia.github.io/mocha/) testing framework.
|
475 |
|
476 | Also, note that the dependencies object is filled **only** inside the unit test callbacks `it` and
|
477 | setup helpers `beforeEach` and `afterEach`
|
478 |
|
479 | ```js
|
480 | ngDescribe({
|
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
|
503 | angular.module('A', [])
|
504 | .value('foo', 'bar');
|
505 | // A-spec.js
|
506 | ngDescribe({
|
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 |
|
521 | We can easily test a built-in or custom filter function
|
522 |
|
523 | ```js
|
524 | ngDescribe({
|
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 |
|
538 | We 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
|
542 | angular.module('B', ['A'])
|
543 | .service('addFoo', function (foo) {
|
544 | return function (str) {
|
545 | return str + foo;
|
546 | };
|
547 | });
|
548 | // B-spec.js
|
549 | ngDescribe({
|
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 |
|
567 | We can easily create instances of controller functions and scope objects.
|
568 | In 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
|
572 | angular.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 | });
|
581 | ngDescribe({
|
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
|
604 | angular.module('MyFoo', [])
|
605 | .directive('myFoo', function () {
|
606 | return {
|
607 | restrict: 'E',
|
608 | replace: true,
|
609 | template: '<span>{{ bar }}</span>'
|
610 | };
|
611 | });
|
612 | ngDescribe({
|
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 |
|
630 | If 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
|
634 | angular.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 |
|
664 | If you add methods to the controller inside custom directive, use `controllerAs` syntax to
|
665 | expose the controller instance.
|
666 |
|
667 | ```js
|
668 | angular.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 | });
|
680 | ngDescribe({
|
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 |
|
699 | If a directive implements isolate scope, we can configure parent scope separately.
|
700 |
|
701 | ```js
|
702 | angular.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 |
|
715 | We can use `element` together with `parentScope` property to set initial values.
|
716 |
|
717 | ```js
|
718 | ngDescribe({
|
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 |
|
733 | We can change parent's values to observe propagation into the directive
|
734 |
|
735 | ```js
|
736 | // same setup
|
737 | it('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 |
|
747 | You can use multiple `beforeEach` and `afterEach` inside `tests` function.
|
748 |
|
749 | ```js
|
750 | ngDescribe({
|
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 |
|
773 | This could be useful for setting up additional mocks, like `$httpBackend`.
|
774 |
|
775 | ```js
|
776 | angular.module('apiCaller', [])
|
777 | .service('getIt', function ($http) {
|
778 | return function () {
|
779 | return $http.get('/my/url');
|
780 | };
|
781 | });
|
782 | ngDescribe({
|
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
|
807 | is created. This gives you a chance to setup mocks before running the element and possibly making calls.
|
808 | If 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 |
|
815 | Often during testing we need to mock something provided by a module, even if it is
|
816 | passed via dependency injection. ng-describe makes it very simple. List all modules with values
|
817 | to be mocked in `mocks` object property.
|
818 |
|
819 | ```js
|
820 | // C.js
|
821 | angular.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
|
829 | ngDescribe({
|
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 |
|
851 | Remember when making mocks, it is always `module name : provider name : mocked property name`
|
852 |
|
853 | ```js
|
854 | mocks: {
|
855 | 'module name': {
|
856 | 'mocked provider name': {
|
857 | 'mocked value name'
|
858 | }
|
859 | }
|
860 | }
|
861 | ```
|
862 |
|
863 | Note: the mocked values are injected using `$provider.constant` call to be able to override both
|
864 | values and constants
|
865 |
|
866 | ```js
|
867 | angular.module('A10', [])
|
868 | .constant('foo', 'bar');
|
869 | ngDescribe({
|
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 |
|
885 | You can even mock part of the module itself and use mock value in other parts via injection
|
886 |
|
887 | ```js
|
888 | angular.module('LargeModule', [])
|
889 | .constant('foo', 'foo')
|
890 | .service('getFoo', function (foo) {
|
891 | return function getFoo() {
|
892 | return foo;
|
893 | };
|
894 | });
|
895 | ngDescribe({
|
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 |
|
914 | You can use other injected dependencies inside mocked functions, using
|
915 | injected values and free parameters.
|
916 |
|
917 | ```js
|
918 | ngDescribe({
|
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 |
|
943 | Often we need some dummy response from `$http.get` method. We can use mock `httpBackend`
|
944 | or mock the `$http` object. For example to always return mock value when making any GET request,
|
945 | we can use
|
946 |
|
947 | ```js
|
948 | mocks: {
|
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
|
965 | is placed into the `data` property, as I show here.
|
966 |
|
967 | #### Mock http responses
|
968 |
|
969 | You can use a shortcut to define mock HTTP responses via `$httpBackend` module. For example,
|
970 | you can define static responses.
|
971 |
|
972 | ```js
|
973 | ngDescribe({
|
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 | ```
|
988 | All HTTP methods are supported (`get`, `post`, `delete`, `put`, etc.).
|
989 |
|
990 | You can also get a function that would return a config object.
|
991 |
|
992 | ```js
|
993 | var mockGetApi = {
|
994 | '/some/url': 42
|
995 | };
|
996 | mockGetApi['/some/other/url'] = [500, 'not ok'];
|
997 | ngDescribe({
|
998 | http: {
|
999 | get: mockGetApi
|
1000 | }
|
1001 | });
|
1002 | ```
|
1003 |
|
1004 | You can use `deps.http.flush()` to move the http responses along.
|
1005 |
|
1006 | You can return the entire http mock object from a function, or combine objects with functions.
|
1007 |
|
1008 | ```js
|
1009 | function constructMockApi() {
|
1010 | return {
|
1011 | get: function () {
|
1012 | return { '/my/url': 42 };
|
1013 | },
|
1014 | post: {
|
1015 | '/my/other/url': [200, 'nice']
|
1016 | }
|
1017 | };
|
1018 | }
|
1019 | ngDescribe({
|
1020 | http: constructMockApi,
|
1021 | test: function (deps) {
|
1022 | ...
|
1023 | }
|
1024 | });
|
1025 | ```
|
1026 |
|
1027 | You can use exact query arguments too
|
1028 |
|
1029 | ```js
|
1030 | http: {
|
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 |
|
1040 | or you can build the query string automatically by passing `params` property in the request config
|
1041 | objet
|
1042 |
|
1043 | ```js
|
1044 | http: {
|
1045 | get: {
|
1046 | '/foo/bar?search=value&something=else': 'foo'
|
1047 | }
|
1048 | }
|
1049 | // inside the unit test
|
1050 | var 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,
|
1062 | which are looser than `$httpBackend.expect(method, ...)`,
|
1063 | see [ngMock/$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).
|
1064 |
|
1065 | ### Spying
|
1066 |
|
1067 | #### Spy on injected methods
|
1068 |
|
1069 | One can quickly spy on injected services (or other methods) using [sinon.js](http://sinonjs.org/)
|
1070 | similarly 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)
|
1073 | into the list of loaded Karma files.
|
1074 | * Setup spy in the `beforeEach` function. Since every injected service is a method on the `deps`
|
1075 | object, the setup is a single command.
|
1076 | * Restore the original method in `afterEach` function.
|
1077 |
|
1078 | ```js
|
1079 | // source code
|
1080 | angular.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
|
1091 | ngDescribe({
|
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 |
|
1114 | You can inject a function, but use a [Sinon spy](http://sinonjs.org/docs/#spies) instead
|
1115 | of the injected function to get additional information. For example, to spy on the `$filter uppercase`,
|
1116 | we can use the following code.
|
1117 |
|
1118 | ```js
|
1119 | ngDescribe({
|
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 |
|
1146 | Let us say you need to verify that the `$interval` service injected in the module under test
|
1147 | was called. It is a little verbose to verify from the unit test. We must mock the `$interval`
|
1148 | with our function and then call the actual `$interval` from the module `ng` to provide the
|
1149 | same functionality.
|
1150 |
|
1151 | Source code we are trying to unit test
|
1152 |
|
1153 | ```js
|
1154 | angular.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 |
|
1166 | In the unit test we will mock `$interval` service for module `IntervalExample`
|
1167 |
|
1168 | ```js
|
1169 | // unit test start
|
1170 | var intervalCalled;
|
1171 | ngDescribe({
|
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 |
|
1193 | A unit test just calls the `numbers` function and then checks the variable `intervalCalled`
|
1194 |
|
1195 | ```js
|
1196 | it('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 |
|
1208 | You 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 |
|
1212 | If we mock an injected service, we can still spy on it, just like as if we were spying on the
|
1213 | regular service. For example, let us take the same method as above and mock it.
|
1214 |
|
1215 | ```js
|
1216 | angular.module('Tweets', [])
|
1217 | .service('getTweets', function () {
|
1218 | return function getTweets(username) {
|
1219 | return 42;
|
1220 | };
|
1221 | });
|
1222 | ```
|
1223 |
|
1224 | The mock will return a different number.
|
1225 |
|
1226 | ```js
|
1227 | ngDescribe({
|
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 |
|
1258 | If 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/)),
|
1260 | you can easily configure these modules.
|
1261 |
|
1262 | ```js
|
1263 | angular.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
|
1270 | angular.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
|
1283 | ngDescribe({
|
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 |
|
1301 | You can configure multiple modules at the same time. Note that during the configuration
|
1302 | Angular is yet to be loaded. Thus you cannot use Angular services inside the configuration blocks.
|
1303 |
|
1304 | ### Helpful failure messages
|
1305 |
|
1306 | ng-describe works inside [helpDescribe function](https://github.com/bahmutov/lazy-ass-helpful#lazy-ass-helpful-bdd),
|
1307 | producing meaningful error messages on failure (if you use [lazy assertions](https://github.com/bahmutov/lazy-ass)).
|
1308 |
|
1309 | ```js
|
1310 | helpDescribe('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 | ```
|
1322 | when this test fails, it generates meaningful message with all relevant information: the expression
|
1323 | that 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 |
|
1336 | To build the README document, run unit tests and linter
|
1337 |
|
1338 | npm run build
|
1339 |
|
1340 | To run all unit tests (against different Angular versions)
|
1341 |
|
1342 | npm test
|
1343 |
|
1344 | To keep a watch and rerun build + lint + tests on source file change
|
1345 |
|
1346 | npm run watch
|
1347 |
|
1348 | For now, all source is in a single `ng-describe.js` file, while the documentation
|
1349 | is generated from Markdown files in the `docs` folder
|
1350 |
|
1351 | To just run karma unit tests via Grunt plugin
|
1352 |
|
1353 | npm run karma
|
1354 |
|
1355 | If 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 |
|
1362 | Author: Kensho © 2014
|
1363 |
|
1364 | * [@kensho](https://twitter.com/kensho)
|
1365 | * [kensho.com](http://kensho.com)
|
1366 |
|
1367 | Support: if you find any problems with this library,
|
1368 | [open issue](https://github.com/kensho/ng-describe/issues) on Github
|
1369 |
|
1370 |
|
1371 | The MIT License (MIT)
|
1372 |
|
1373 | Copyright (c) 2014 Kensho
|
1374 |
|
1375 | Permission is hereby granted, free of charge, to any person obtaining a copy of
|
1376 | this software and associated documentation files (the "Software"), to deal in
|
1377 | the Software without restriction, including without limitation the rights to
|
1378 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
1379 | the Software, and to permit persons to whom the Software is furnished to do so,
|
1380 | subject to the following conditions:
|
1381 |
|
1382 | The above copyright notice and this permission notice shall be included in all
|
1383 | copies or substantial portions of the Software.
|
1384 |
|
1385 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
1386 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
1387 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
1388 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
1389 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
1390 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1391 |
|
1392 |
|
1393 |
|