1 | # Examples
|
2 |
|
3 | Some examples use Jasmine matchers, others use `la` assertion from
|
4 | [lazy-ass](https://github.com/bahmutov/lazy-ass) library and *done* callback argument
|
5 | from [Mocha](http://visionmedia.github.io/mocha/) testing framework.
|
6 |
|
7 | Also, note that the dependencies object is filled **only** inside the unit test callbacks `it` and
|
8 | setup helpers `beforeEach` and `afterEach`
|
9 |
|
10 | ```js
|
11 | ngDescribe({
|
12 | inject: 'foo',
|
13 | tests: function (deps) {
|
14 | // deps is an empty object here
|
15 | beforeEach(function () {
|
16 | // deps object has 'foo'
|
17 | });
|
18 | // deps is an empty object here
|
19 | it(function () {
|
20 | // deps object has 'foo'
|
21 | });
|
22 | // deps is an empty object here
|
23 | afterEach(function () {
|
24 | // deps object has 'foo'
|
25 | });
|
26 | }
|
27 | });
|
28 | ```
|
29 |
|
30 | ## Test value provided by a module
|
31 |
|
32 | ```js
|
33 | // A.js
|
34 | angular.module('A', [])
|
35 | .value('foo', 'bar');
|
36 | // A-spec.js
|
37 | ngDescribe({
|
38 | name: 'test value',
|
39 | modules: 'A',
|
40 | inject: 'foo',
|
41 | tests: function (deps) {
|
42 | // deps object has every injected dependency as a property
|
43 | it('has correct value foo', function () {
|
44 | expect(deps.foo).toEqual('bar');
|
45 | });
|
46 | }
|
47 | });
|
48 | ```
|
49 |
|
50 | ## Test a filter
|
51 |
|
52 | We can easily test a built-in or custom filter function
|
53 |
|
54 | ```js
|
55 | ngDescribe({
|
56 | name: 'built-in filter',
|
57 | inject: '$filter',
|
58 | tests: function (deps) {
|
59 | it('can convert to lowercase', function () {
|
60 | var lowercase = deps.$filter('lowercase');
|
61 | la(lowercase('Foo') === 'foo');
|
62 | });
|
63 | }
|
64 | });
|
65 | ```
|
66 |
|
67 | ## Test a service
|
68 |
|
69 | We can inject a service to test using the same approach. You can even use multiple specs inside `tests` callback.
|
70 |
|
71 | ```js
|
72 | // B.js
|
73 | angular.module('B', ['A'])
|
74 | .service('addFoo', function (foo) {
|
75 | return function (str) {
|
76 | return str + foo;
|
77 | };
|
78 | });
|
79 | // B-spec.js
|
80 | ngDescribe({
|
81 | name: 'service tests',
|
82 | modules: 'B',
|
83 | inject: 'addFoo',
|
84 | tests: function (deps) {
|
85 | it('is a function', function () {
|
86 | expect(typeof deps.addFoo).toEqual('function');
|
87 | });
|
88 | it('appends value of foo to any string', function () {
|
89 | var result = deps.addFoo('x');
|
90 | expect(result).toEqual('xbar');
|
91 | });
|
92 | }
|
93 | });
|
94 | ```
|
95 |
|
96 | ## Test controller and scope
|
97 |
|
98 | We can easily create instances of controller functions and scope objects.
|
99 | In this example we also inject `$timeout` service to speed up delayed actions
|
100 | (see [Testing Angular async stuff](http://glebbahmutov.com/blog/testing-angular-async-stuff/)).
|
101 |
|
102 | ```js
|
103 | angular.module('S', [])
|
104 | .controller('sample', function ($timeout, $scope) {
|
105 | $scope.foo = 'foo';
|
106 | $scope.update = function () {
|
107 | $timeout(function () {
|
108 | $scope.foo = 'bar';
|
109 | }, 1000);
|
110 | };
|
111 | });
|
112 | ngDescribe({
|
113 | name: 'timeout in controller',
|
114 | modules: 'S',
|
115 | // inject $timeout so we can flush the timeout queue
|
116 | inject: ['$timeout'],
|
117 | controllers: 'sample',
|
118 | tests: function (deps) {
|
119 | // deps.sample = $scope object injected into sample controller
|
120 | it('has initial values', function () {
|
121 | la(deps.sample.foo === 'foo');
|
122 | });
|
123 | it('updates after timeout', function () {
|
124 | deps.sample.update();
|
125 | deps.$timeout.flush();
|
126 | la(deps.sample.foo === 'bar');
|
127 | });
|
128 | }
|
129 | });
|
130 | ```
|
131 |
|
132 | ## Test directive
|
133 |
|
134 | ```js
|
135 | angular.module('MyFoo', [])
|
136 | .directive('myFoo', function () {
|
137 | return {
|
138 | restrict: 'E',
|
139 | replace: true,
|
140 | template: '<span>{{ bar }}</span>'
|
141 | };
|
142 | });
|
143 | ngDescribe({
|
144 | name: 'MyFoo directive',
|
145 | modules: 'MyFoo',
|
146 | element: '<my-foo></my-foo>',
|
147 | tests: function (deps) {
|
148 | it('can update DOM using binding', function () {
|
149 | la(check.has(deps, 'element'), 'has compiled element');
|
150 | var scope = deps.element.scope();
|
151 | scope.bar = 'bar';
|
152 | scope.$apply();
|
153 | la(deps.element.html() === 'bar');
|
154 | });
|
155 | }
|
156 | });
|
157 | ```
|
158 |
|
159 | ## Test controllerAs syntax
|
160 |
|
161 | If you use `controllerAs` syntax without any components (see [Binding to ...][binding] post or
|
162 | [Separate ...][separate]), then you can still test it quickly
|
163 |
|
164 | ```js
|
165 | angular.module('H', [])
|
166 | .controller('hController', function () {
|
167 | // notice we attach properties to the instance, not to the $scope
|
168 | this.foo = 'foo';
|
169 | });
|
170 | ngDescribe({
|
171 | module: 'H',
|
172 | element: '<div ng-controller="hController as ctrl">{{ ctrl.foo }}</div>',
|
173 | tests: function (deps) {
|
174 | it('created controller correctly', function () {
|
175 | var compiledHtml = deps.element.html();
|
176 | // 'foo'
|
177 | });
|
178 | it('changes value', function () {
|
179 | var ctrl = deps.element.controller();
|
180 | // { foo: 'foo' }
|
181 | ctrl.foo = 'bar';
|
182 | deps.element.scope().$apply();
|
183 | var compiledHtml = deps.element.html();
|
184 | // 'bar'
|
185 | });
|
186 | }
|
187 | });
|
188 | ```
|
189 |
|
190 | [binding]: http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
|
191 | [separate]: http://glebbahmutov.com/blog/separate-model-from-view-in-angular/
|
192 |
|
193 | ## Test controller instance in custom directive
|
194 |
|
195 | If you add methods to the controller inside custom directive, use `controllerAs` syntax to
|
196 | expose the controller instance.
|
197 |
|
198 | ```js
|
199 | angular.module('C', [])
|
200 | .directive('cDirective', function () {
|
201 | return {
|
202 | controllerAs: 'ctrl', // puts controller instance onto scope as ctrl
|
203 | controller: function ($scope) {
|
204 | $scope.foo = 'foo';
|
205 | this.foo = function getFoo() {
|
206 | return $scope.foo;
|
207 | };
|
208 | }
|
209 | };
|
210 | });
|
211 | ngDescribe({
|
212 | name: 'controller for directive instance',
|
213 | modules: 'C',
|
214 | element: '<c-directive></c-directive>',
|
215 | tests: function (deps) {
|
216 | it('has controller', function () {
|
217 | var scope = deps.element.scope(); // grabs scope
|
218 | var controller = scope.ctrl; // grabs controller instance
|
219 | la(typeof controller.foo === 'function');
|
220 | la(controller.foo() === 'foo');
|
221 | scope.foo = 'bar';
|
222 | la(controller.foo() === 'bar');
|
223 | });
|
224 | }
|
225 | });
|
226 | ```
|
227 |
|
228 | ## Test 2 way binding
|
229 |
|
230 | If a directive implements isolate scope, we can configure parent scope separately.
|
231 |
|
232 | ```js
|
233 | angular.module('IsolateFoo', [])
|
234 | .directive('aFoo', function () {
|
235 | return {
|
236 | restrict: 'E',
|
237 | replace: true,
|
238 | scope: {
|
239 | bar: '='
|
240 | },
|
241 | template: '<span>{{ bar }}</span>'
|
242 | };
|
243 | });
|
244 | ```
|
245 |
|
246 | We can use `element` together with `parentScope` property to set initial values.
|
247 |
|
248 | ```js
|
249 | ngDescribe({
|
250 | modules: 'IsolateFoo',
|
251 | element: '<a-foo bar="x"></a-foo>',
|
252 | parentScope: {
|
253 | x: 'initial'
|
254 | },
|
255 | tests: function (deps) {
|
256 | it('has correct initial value', function () {
|
257 | var scope = deps.element.isolateScope();
|
258 | expect(scope.bar).toEqual('initial');
|
259 | });
|
260 | }
|
261 | });
|
262 | ```
|
263 |
|
264 | We can change parent's values to observe propagation into the directive
|
265 |
|
266 | ```js
|
267 | // same setup
|
268 | it('updates isolate scope', function () {
|
269 | deps.parentScope.x = 42;
|
270 | deps.$rootScope.$apply();
|
271 | var scope = deps.element.isolateScope();
|
272 | expect(scope.bar).toEqual(42);
|
273 | });
|
274 | ```
|
275 |
|
276 | ## beforeEach and afterEach
|
277 |
|
278 | You can use multiple `beforeEach` and `afterEach` inside `tests` function.
|
279 |
|
280 | ```js
|
281 | ngDescribe({
|
282 | name: 'before and after example',
|
283 | modules: ['A'],
|
284 | inject: ['foo'],
|
285 | tests: function (deps) {
|
286 | var localFoo;
|
287 | beforeEach(function () {
|
288 | // dependencies are already injected
|
289 | la(deps.foo === 'bar');
|
290 | localFoo = deps.foo;
|
291 | });
|
292 | it('has correct value foo', function () {
|
293 | la(localFoo === 'bar');
|
294 | });
|
295 | afterEach(function () {
|
296 | la(localFoo === 'bar');
|
297 | // dependencies are still available
|
298 | la(deps.foo === 'bar');
|
299 | });
|
300 | }
|
301 | });
|
302 | ```
|
303 |
|
304 | This could be useful for setting up additional mocks, like `$httpBackend`.
|
305 |
|
306 | ```js
|
307 | angular.module('apiCaller', [])
|
308 | .service('getIt', function ($http) {
|
309 | return function () {
|
310 | return $http.get('/my/url');
|
311 | };
|
312 | });
|
313 | ngDescribe({
|
314 | name: 'http mock backend example',
|
315 | modules: ['apiCaller'],
|
316 | inject: ['getIt', '$httpBackend'],
|
317 | tests: function (deps) {
|
318 | beforeEach(function () {
|
319 | deps.$httpBackend.expectGET('/my/url').respond(200, 42);
|
320 | });
|
321 | it('returns result from server', function (done) {
|
322 | deps.getIt().then(function (response) {
|
323 | la(response && response.status === 200);
|
324 | la(response.data === 42);
|
325 | done();
|
326 | });
|
327 | deps.$httpBackend.flush();
|
328 | });
|
329 | afterEach(function () {
|
330 | deps.$httpBackend.verifyNoOutstandingRequest();
|
331 | deps.$httpBackend.verifyNoOutstandingExpectation();
|
332 | });
|
333 | }
|
334 | });
|
335 | ```
|
336 |
|
337 | **Note** if you use `beforeEach` block with `element`, the `beforeEach` runs *before* the element
|
338 | is created. This gives you a chance to setup mocks before running the element and possibly making calls.
|
339 | If you really want to control when an element is created use `exposeApi` option
|
340 | (see [Secondary options](#secondary-options)).
|
341 |
|
342 | ## Mocking
|
343 |
|
344 | ### Mock value provided by a module
|
345 |
|
346 | Often during testing we need to mock something provided by a module, even if it is
|
347 | passed via dependency injection. {%= name %} makes it very simple. List all modules with values
|
348 | to be mocked in `mocks` object property.
|
349 |
|
350 | ```js
|
351 | // C.js
|
352 | angular.module('C', ['A'])
|
353 | .service('getFoo', function (foo) {
|
354 | // foo is provided by module A
|
355 | return function getFoo() {
|
356 | return foo;
|
357 | };
|
358 | });
|
359 | // C-spec.js
|
360 | ngDescribe({
|
361 | name: 'test C with mocking top level',
|
362 | modules: ['C'],
|
363 | inject: ['getFoo'],
|
364 | mocks: {
|
365 | // replace C.getFoo with mock function that returns 11
|
366 | C: {
|
367 | getFoo: function () {
|
368 | return 11;
|
369 | }
|
370 | }
|
371 | },
|
372 | verbose: false,
|
373 | tests: function (deps) {
|
374 | it('has mock injected value', function () {
|
375 | var result = deps.getFoo();
|
376 | la(result === 11, 'we got back mock value', result);
|
377 | });
|
378 | }
|
379 | });
|
380 | ```
|
381 |
|
382 | Remember when making mocks, it is always `module name : provider name : mocked property name`
|
383 |
|
384 | ```js
|
385 | mocks: {
|
386 | 'module name': {
|
387 | 'mocked provider name': {
|
388 | 'mocked value name'
|
389 | }
|
390 | }
|
391 | }
|
392 | ```
|
393 |
|
394 | Note: the mocked values are injected using `$provider.constant` call to be able to override both
|
395 | values and constants
|
396 |
|
397 | ```js
|
398 | angular.module('A10', [])
|
399 | .constant('foo', 'bar');
|
400 | ngDescribe({
|
401 | modules: 'A10',
|
402 | mock: {
|
403 | A10: {
|
404 | foo: 42
|
405 | }
|
406 | },
|
407 | inject: 'foo',
|
408 | tests: function (deps) {
|
409 | it('has correct constant foo', function () {
|
410 | expect(deps.foo).toEqual(42);
|
411 | });
|
412 | }
|
413 | });
|
414 | ```
|
415 |
|
416 | You can even mock part of the module itself and use mock value in other parts via injection
|
417 |
|
418 | ```js
|
419 | angular.module('LargeModule', [])
|
420 | .constant('foo', 'foo')
|
421 | .service('getFoo', function (foo) {
|
422 | return function getFoo() {
|
423 | return foo;
|
424 | };
|
425 | });
|
426 | ngDescribe({
|
427 | name: 'mocking part of the module itself',
|
428 | modules: 'LargeModule',
|
429 | inject: 'getFoo',
|
430 | mock: {
|
431 | LargeModule: {
|
432 | foo: 'bar'
|
433 | }
|
434 | },
|
435 | tests: function (deps) {
|
436 | it('service injects mock value', function () {
|
437 | la(deps.getFoo() === 'bar', 'returns mock value');
|
438 | });
|
439 | }
|
440 | });
|
441 | ```
|
442 |
|
443 | ### Angular services inside mocks
|
444 |
|
445 | You can use other injected dependencies inside mocked functions, using
|
446 | injected values and free parameters.
|
447 |
|
448 | ```js
|
449 | ngDescribe({
|
450 | inject: ['getFoo', '$rootScope'],
|
451 | mocks: {
|
452 | C: {
|
453 | // use angular $q service in the mock function
|
454 | // argument "value" remains free
|
455 | getFoo: function ($q, value) {
|
456 | return $q.when(value);
|
457 | }
|
458 | }
|
459 | },
|
460 | tests: function (deps) {
|
461 | it('injected $q into mock', function (done) {
|
462 | deps.getFoo('foo').then(function (result) {
|
463 | expect(result).toEqual('foo');
|
464 | done();
|
465 | });
|
466 | deps.$rootScope.$apply(); // resolve promise
|
467 | });
|
468 | }
|
469 | });
|
470 | ```
|
471 |
|
472 | ### Mock $http.get
|
473 |
|
474 | Often we need some dummy response from `$http.get` method. We can use mock `httpBackend`
|
475 | or mock the `$http` object. For example to always return mock value when making any GET request,
|
476 | we can use
|
477 |
|
478 | ```js
|
479 | mocks: {
|
480 | ng: {
|
481 | $http: {
|
482 | get: function ($q, url) {
|
483 | // inspect url if needed
|
484 | return $q.when({
|
485 | data: {
|
486 | life: 42
|
487 | }
|
488 | });
|
489 | }
|
490 | }
|
491 | }
|
492 | }
|
493 | ```
|
494 |
|
495 | `$http` service returns a promise that resolves with a *response* object. The actual result to send
|
496 | is placed into the `data` property, as I show here.
|
497 |
|
498 | ### Mock http responses
|
499 |
|
500 | You can use a shortcut to define mock HTTP responses via `$httpBackend` module. For example,
|
501 | you can define static responses.
|
502 |
|
503 | ```js
|
504 | ngDescribe({
|
505 | http: {
|
506 | get: {
|
507 | '/some/url': 42,
|
508 | '/some/other/url': [500, 'something went wrong']
|
509 | },
|
510 | post: {
|
511 | // you can use custom functions too
|
512 | '/some/post/url': function (method, url, data, headers) {
|
513 | return [200, 'ok'];
|
514 | }
|
515 | }
|
516 | }
|
517 | });
|
518 | ```
|
519 | All HTTP methods are supported (`get`, `post`, `delete`, `put`, etc.).
|
520 |
|
521 | You can also get a function that would return a config object.
|
522 |
|
523 | ```js
|
524 | var mockGetApi = {
|
525 | '/some/url': 42
|
526 | };
|
527 | mockGetApi['/some/other/url'] = [500, 'not ok'];
|
528 | ngDescribe({
|
529 | http: {
|
530 | get: mockGetApi
|
531 | }
|
532 | });
|
533 | ```
|
534 |
|
535 | You can use `deps.http.flush()` to move the http responses along.
|
536 |
|
537 | You can return the entire http mock object from a function, or combine objects with functions.
|
538 |
|
539 | ```js
|
540 | function constructMockApi() {
|
541 | return {
|
542 | get: function () {
|
543 | return { '/my/url': 42 };
|
544 | },
|
545 | post: {
|
546 | '/my/other/url': [200, 'nice']
|
547 | }
|
548 | };
|
549 | }
|
550 | ngDescribe({
|
551 | http: constructMockApi,
|
552 | test: function (deps) {
|
553 | ...
|
554 | }
|
555 | });
|
556 | ```
|
557 |
|
558 | You can use exact query arguments too
|
559 |
|
560 | ```js
|
561 | http: {
|
562 | get: {
|
563 | '/foo/bar?search=value': 42,
|
564 | '/foo/bar?search=value&something=else': 'foo'
|
565 | }
|
566 | }
|
567 | // $http.get('/foo/bar?search=value') will resolve with value 42
|
568 | // $http.get('/foo/bar?search=value&something=else') will resolve with value 'foo'
|
569 | ```
|
570 |
|
571 | or you can build the query string automatically by passing `params` property in the request config
|
572 | objet
|
573 |
|
574 | ```js
|
575 | http: {
|
576 | get: {
|
577 | '/foo/bar?search=value&something=else': 'foo'
|
578 | }
|
579 | }
|
580 | // inside the unit test
|
581 | var config = {
|
582 | params: {
|
583 | search: 'value',
|
584 | something: 'else'
|
585 | }
|
586 | };
|
587 | $http.get('/foo/bar', config).then(function (response) {
|
588 | // response.data = 'foo'
|
589 | });
|
590 | ```
|
591 |
|
592 | **note** the `http` mocks are defined using `$httpBack.when(method, ...)` calls,
|
593 | which are looser than `$httpBackend.expect(method, ...)`,
|
594 | see [ngMock/$httpBackend](https://docs.angularjs.org/api/ngMock/service/$httpBackend).
|
595 |
|
596 | ## Spying
|
597 |
|
598 | ### Spy on injected methods
|
599 |
|
600 | One can quickly spy on injected services (or other methods) using [sinon.js](http://sinonjs.org/)
|
601 | similarly to [spying on the regular JavaScript methods](http://glebbahmutov.com/blog/spying-on-methods/).
|
602 |
|
603 | * Include a browser-compatible combined [sinon.js build](http://sinonjs.org/releases/sinon-1.12.1.js)
|
604 | into the list of loaded Karma files.
|
605 | * Setup spy in the `beforeEach` function. Since every injected service is a method on the `deps`
|
606 | object, the setup is a single command.
|
607 | * Restore the original method in `afterEach` function.
|
608 |
|
609 | ```js
|
610 | // source code
|
611 | angular.module('Tweets', [])
|
612 | .service('getTweets', function () {
|
613 | return function getTweets(username) {
|
614 | console.log('returning # of tweets for', username);
|
615 | return 42;
|
616 | };
|
617 | });
|
618 | ```
|
619 |
|
620 | ```js
|
621 | // spec
|
622 | ngDescribe({
|
623 | name: 'spying on Tweets getTweets service',
|
624 | modules: 'Tweets',
|
625 | inject: 'getTweets',
|
626 | tests: function (deps) {
|
627 | beforeEach(function () {
|
628 | sinon.spy(deps, 'getTweets');
|
629 | });
|
630 | afterEach(function () {
|
631 | deps.getTweets.restore();
|
632 | });
|
633 | it('calls getTweets service', function () {
|
634 | var n = deps.getTweets('foo');
|
635 | la(n === 42, 'resolved with correct value');
|
636 | la(deps.getTweets.called, 'getTweets was called (spied using sinon)');
|
637 | la(deps.getTweets.firstCall.calledWith('foo'));
|
638 | });
|
639 | }
|
640 | });
|
641 | ```
|
642 |
|
643 | ### Spy on injected function
|
644 |
|
645 | You can inject a function, but use a [Sinon spy](http://sinonjs.org/docs/#spies) instead
|
646 | of the injected function to get additional information. For example, to spy on the `$filter uppercase`,
|
647 | we can use the following code.
|
648 |
|
649 | ```js
|
650 | ngDescribe({
|
651 | name: 'spying on a filter',
|
652 | inject: '$filter',
|
653 | tests: function (deps) {
|
654 | /*
|
655 | to spy on a injected filter, need to grab the actual filter function
|
656 | and then create a spy
|
657 | */
|
658 | // _uppercase = angular uppercase $filter
|
659 | // uppercase = spy on the _uppercase
|
660 | var _uppercase, uppercase;
|
661 | beforeEach(function () {
|
662 | _uppercase = deps.$filter('uppercase');
|
663 | uppercase = sinon.spy(_uppercase);
|
664 | });
|
665 | it('converts string to uppercase', function () {
|
666 | var result = uppercase('foo');
|
667 | la(result === 'FOO', 'converted string to uppercase', result);
|
668 | la(uppercase.calledOnce, 'uppercase was called once');
|
669 | la(uppercase.calledWith('foo'));
|
670 | });
|
671 | }
|
672 | });
|
673 | ```
|
674 |
|
675 | ### Spy on 3rd party service injected some place else
|
676 |
|
677 | Let us say you need to verify that the `$interval` service injected in the module under test
|
678 | was called. It is a little verbose to verify from the unit test. We must mock the `$interval`
|
679 | with our function and then call the actual `$interval` from the module `ng` to provide the
|
680 | same functionality.
|
681 |
|
682 | Source code we are trying to unit test
|
683 |
|
684 | ```js
|
685 | angular.module('IntervalExample', [])
|
686 | .service('numbers', function ($interval, $rootScope) {
|
687 | return function emitNumbers(delay, n) {
|
688 | var k = 0;
|
689 | $interval(function () {
|
690 | $rootScope.$emit('number', k);
|
691 | k += 1;
|
692 | }, 100, n);
|
693 | };
|
694 | });
|
695 | ```
|
696 |
|
697 | In the unit test we will mock `$interval` service for module `IntervalExample`
|
698 |
|
699 | ```js
|
700 | // unit test start
|
701 | var intervalCalled;
|
702 | ngDescribe({
|
703 | name: 'spying on $interval',
|
704 | module: 'IntervalExample',
|
705 | inject: ['numbers', '$rootScope'],
|
706 | verbose: false,
|
707 | only: false,
|
708 | mocks: {
|
709 | IntervalExample: {
|
710 | $interval: function mockInterval(fn, delay, n) {
|
711 | var injector = angular.injector(['ng']);
|
712 | var $interval = injector.get('$interval');
|
713 | intervalCalled = true;
|
714 | return $interval(fn, delay, n);
|
715 | }
|
716 | }
|
717 | },
|
718 | tests: function (deps) {
|
719 | // unit test goes here
|
720 | }
|
721 | });
|
722 | ```
|
723 |
|
724 | A unit test just calls the `numbers` function and then checks the variable `intervalCalled`
|
725 |
|
726 | ```js
|
727 | it('emits 3 numbers', function (done) {
|
728 | deps.$rootScope.$on('number', function (event, k) {
|
729 | if (k === 2) {
|
730 | done();
|
731 | }
|
732 | });
|
733 | // emit 3 numbers with 100ms interval
|
734 | deps.numbers(100, 3);
|
735 | la(intervalCalled, 'the $interval was called somewhere');
|
736 | });
|
737 | ```
|
738 |
|
739 | You can see the unit test in file [test/spying-on-interval-spec.js](test/spying-on-interval-spec.js).
|
740 |
|
741 | ### Spy on mocked service
|
742 |
|
743 | If we mock an injected service, we can still spy on it, just like as if we were spying on the
|
744 | regular service. For example, let us take the same method as above and mock it.
|
745 |
|
746 | ```js
|
747 | angular.module('Tweets', [])
|
748 | .service('getTweets', function () {
|
749 | return function getTweets(username) {
|
750 | return 42;
|
751 | };
|
752 | });
|
753 | ```
|
754 |
|
755 | The mock will return a different number.
|
756 |
|
757 | ```js
|
758 | ngDescribe({
|
759 | name: 'spying on mock methods',
|
760 | inject: 'getTweets',
|
761 | mocks: {
|
762 | Tweets: {
|
763 | getTweets: function (username) {
|
764 | return 1000;
|
765 | }
|
766 | }
|
767 | },
|
768 | tests: function (deps) {
|
769 | beforeEach(function () {
|
770 | sinon.spy(deps, 'getTweets');
|
771 | });
|
772 | afterEach(function () {
|
773 | deps.getTweets.restore();
|
774 | });
|
775 | it('calls mocked getTweets service', function () {
|
776 | var n = deps.getTweets('bar');
|
777 | la(n === 1000, 'resolved with correct value from the mock service');
|
778 | la(deps.getTweets.called,
|
779 | 'mock service getTweets was called (spied using sinon)');
|
780 | la(deps.getTweets.firstCall.calledWith('bar'),
|
781 | 'mock service getTweets was called with expected argument');
|
782 | });
|
783 | }
|
784 | });
|
785 | ```
|
786 |
|
787 | ## Configure module
|
788 |
|
789 | If you use a separate module with namesake provider to pass configuration into the modules
|
790 | (see [Inject valid constants into Angular](http://glebbahmutov.com/blog/inject-valid-constants-into-angular/)),
|
791 | you can easily configure these modules.
|
792 |
|
793 | ```js
|
794 | angular.module('App', ['AppConfig'])
|
795 | .service('foo', function (AppConfig) {
|
796 | return function foo() {
|
797 | return GConfig.bar;
|
798 | };
|
799 | });
|
800 | // config module has provider with same name
|
801 | angular.module('AppConfig', [])
|
802 | .provider('AppConfig', function () {
|
803 | var config = {};
|
804 | return {
|
805 | set: function (settings) {
|
806 | config = settings;
|
807 | },
|
808 | $get: function () {
|
809 | return config;
|
810 | }
|
811 | };
|
812 | });
|
813 | // spec file
|
814 | ngDescribe({
|
815 | name: 'config module example',
|
816 | modules: 'App',
|
817 | inject: 'foo',
|
818 | configs: {
|
819 | // every config module will be loaded automatically
|
820 | AppConfig: {
|
821 | bar: 'boo!'
|
822 | }
|
823 | },
|
824 | tests: function (deps) {
|
825 | it('foo has configured bar value', function () {
|
826 | expect(deps.foo()).toEqual('boo!');
|
827 | });
|
828 | }
|
829 | });
|
830 | ```
|
831 |
|
832 | You can configure multiple modules at the same time. Note that during the configuration
|
833 | Angular is yet to be loaded. Thus you cannot use Angular services inside the configuration blocks.
|
834 |
|
835 | ## Helpful failure messages
|
836 |
|
837 | {%= name %} works inside [helpDescribe function](https://github.com/bahmutov/lazy-ass-helpful#lazy-ass-helpful-bdd),
|
838 | producing meaningful error messages on failure (if you use [lazy assertions](https://github.com/bahmutov/lazy-ass)).
|
839 |
|
840 | ```js
|
841 | helpDescribe('ngDescribe inside helpful', function () {
|
842 | ngDescribe({
|
843 | name: 'example',
|
844 | tests: function () {
|
845 | it('gives helpful error message', function () {
|
846 | var foo = 2, bar = 3;
|
847 | la(foo + bar === 4); // wrong on purpose
|
848 | });
|
849 | }
|
850 | });
|
851 | });
|
852 | ```
|
853 | when this test fails, it generates meaningful message with all relevant information: the expression
|
854 | that fails `foo + bar === 4` and runtime values of `foo` and `bar`.
|
855 |
|
856 | PhantomJS 1.9.7 (Mac OS X)
|
857 | ட ngDescribe inside helpful
|
858 | ட example
|
859 | ட ✘ gives helpful error message FAILED
|
860 | Error: condition [foo + bar === 4] foo: 2 bar: 3
|
861 | at lazyAss (/ng-describe/node_modules/lazy-ass/index.js:57)
|
862 | PhantomJS 1.9.7 (Mac OS X): Executed 37 of 38 (1 FAILED) (skipped 1) (0.053 secs / 0.002 secs)
|