Exploring the power of AngularJS and Jasmine: testing multiple instances of a service in action

Currently, I am immersing myself in the book "Mastering Web Application Development with AngularJS" and came across an example test named 'Aggregating callbacks.'

The specific example causing me troubles involves the Person object:

var Person = function(name, $log) {

    this.eat = function(food) {

        $log.info(name + ' is eating delicious ' + food);

    };

    this.beHungry = function(reason) {

        $log.warn(name + ' is hungry because: ' + reason);

    };

};

This example also includes the Restaurant object:

var Restaurant = function($q, $rootScope) {

    var currentOrder;

    return {

        takeOrder : function(orderedItems) {

            currentOrder = {

                deferred : $q.defer(),
                items : orderedItems

            };

            return currentOrder.deferred.promise;

        },

        deliverOrder : function() {

            currentOrder.deferred.resolve(currentOrder.items);
            $rootScope.$digest();

        },

        problemWithOrder : function(reason) {

            currentOrder.deferred.reject(reason);
            $rootScope.$digest();

        }

    };

};

Lastly, there is a test for aggregating callbacks:

it('should allow callbacks aggregation', function() {

    var pizzaPid = new Restaurant($q, $rootScope);

    var pizzaDelivered = pizzaPid.takeOrder('Margherita');

    pizzaDelivered.then(pawel.eat, pawel.beHungry);
    pizzaDelivered.then(pete.eat, pete.beHungry);

    pizzaPid.deliveryOrder();

    expect($log.info.logs).toContain(['Pawel is eating delicious Margherita']);
    expect($log.info.logs).toContain(['Pete is eating delicious Margherita']);

});

It seems that the test doesn't clarify how items are added or injected into the test. Since TDD is a new concept to me, I decided to convert these global objects to services and factories:

angular.module('myApp', [])

    .service('Person', function(personName, $log) {

        this.eat = function(food) {

            $log.info(personName + ' is eating delicious ' + food);

        };

        this.beHungry = function(reason) {

            $log.warn(personName + ' is hungry because: ' + reason);

        };

    })

    .factory('Restaurant', function($q, $rootScope) {

        var currentOrder;

        return {

            takeOrder : function(orderedItems) {

                currentOrder = {

                    deferred : $q.defer(),
                    items : orderedItems

                };

                return currentOrder.deferred.promise;

            },

            deliverOrder : function() {

                currentOrder.deferred.resolve(currentOrder.items);
                $rootScope.$digest();

            },

            problemWithOrder : function(reason) {

                currentOrder.deferred.reject(reason);
                $rootScope.$digest();

            }

        };

    });

However, I'm now struggling with multiple instances of the service to represent 'pawel' and 'pete' in my test:

describe('Person and Restaurant tests', function() {

    var Person;
    var Restaurant;

    var $q;
    var $rootScope;
    var $log;

    beforeEach(function() {

        module('myApp');

        module(function($provide) {

            $provide.value('personName', 'Pawel');

        });

        inject(function(_Person_, _Restaurant_, _$q_, _$rootScope_, _$log_) {

            Person = _Person_;
            Restaurant = _Restaurant_;

            $q = _$q_;
            $rootScope = _$rootScope_;
            $log = _$log_;

        });

    });

    it('should allow callbacks aggregation', function() {

        var pizzaDelivered = Restaurant.takeOrder('Margherita');
        
        // here's where the problem arises
        // with the current setup, I can only call it as
        // pizzaDelivered.then(Person.eat, Person.beHungry);        
        pizzaDelivered.then(pawel.eat, pawel.beHungry);
        pizzaDelivered.then(pete.eat, pete.beHungry);

        Restaurant.deliveryOrder();

        expect($log.info.logs).toContain(['Pawel is eating delicious Margherita']);
        expect($log.info.logs).toContain(['Pete is eating delicious Margherita']);

    });

});

Given my limited experience with TDD, any assistance would be greatly appreciated.

Answer №1

Why the testing suite only allows

pizzaDelivered.then(Person.eat, Person.beHungry)

is due to the creation of a Person service. In Angular, services are singletons, so the concept of 'Person' may not fully align with the singleton idea. However, it can still be utilized in your application as shown below:

angular.module('app', [])
  .controller('chicago', function($scope, $log) {

      $scope.family = [
         new Person('henry', $log),
         new Person('me', $log),
         new Person('you', $log)
       ];

   });

It is recommended to keep Person and Restaurant as defined in the book. This ensures consistency and alignment with the intended code structure, especially with the unique line:

$rootScope.$digest();

For more information on angular concepts, visit http://docs.angularjs.org/guide/concepts. The digest cycle in Angular updates the DOM and view based on changes made within its context. Invoke $digest() when working outside of the angular context to ensure updates are reflected correctly.

To progress, maintain Person and Restaurant codes as they are and avoid converting them into angular services. Instantiate 2 Person objects before the aggregating callback test using a beforeEach function:

beforeEach(function() {    
    pete = new Person('pete', $log);
    pawel = new Person('pawel', $log);
});

Ensure tests pass by understanding that Person and Restaurant are global functions/constructors, not global objects. Consider working with external code alongside angular in real-world applications like GoogleMaps or Moment for a comprehensive experience.

Looking ahead, future versions of Angular (2.0) may abstract the digest cycle further and introduce an Observe() function. Stay informed for any upcoming changes!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Concealing sections in angular ui.grid by leveraging bootstrap functionality

I'm trying to implement ui-grid in angular js and experimenting with hiding columns using bootstrap classes. For example, imagine a table with 100 columns but on mobile devices, I only want to display 3 of them. Here is a plunker link showcasing my c ...

I am currently utilizing AngularJS alongside Django-cors-headers, which results in the restriction of certain actions for cross-origin requests that necessitate preflight

My Django local server is running on port 8000, and I have a local Nginx server loading an HTML page on port 2080. To resolve cross-domain errors, I have installed the django-cross-header package. In my settings.py, I have configured django-cross-header ...

React Native: Why is useState setter not causing a re-render?

As a beginner in react and javascript, I am facing an issue with showing an ActivityIndicator while logging in a user. The setIsLoading method doesn't seem to change the state and trigger a rerender. When the handleLogin method is called on a button c ...

Is there a way to incorporate a component into Particle.js?

I attempted to encase the Particle around the component but it's not functioning correctly import React from "react"; import { render } from "react-dom"; import Particles from "./Particles"; import "./index.css" ...

What is the best way to save request URLs in JavaScript while following the DRY principle?

Is there a standard practice in JavaScript for storing the URLs of endpoints used in AJAX applications? Would you, for instance, consider creating a "Service" class to encapsulate the URLs? ...

When the child of li is clicked instead

Below is a list I have: <ul id="orderlist"> <li id="2"> <span class="pull-right value">Ready</span> <img src="" class="img-responsive"> Filet Mignon <small>2 servings</small> <small ...

Switching out the background image with a higher resolution one specifically for users with retina devices

I'm trying to create my very first website that is retina ready, but I've run into an issue when it comes to updating images to higher resolutions in CSS. I'm unsure how to go about having a standard image as the background and then switchin ...

Choice of product variations as a package

I am currently working on a project and here is the data structure I have set up for options and combinations: Options: "options": [ { "id": "96ce60e9-b09b-4cf6-aeca-83f75af9ef4b", "position": 0, "name": & ...

The system does not acknowledge 'CI' as a command that can be used internally or externally, functioning program, or batch file

Every time I attempt to execute npm run build in my React project, a persistent error keeps popping up: 'CI' is not recognized as an internal or external command, operable program or batch file. I have exhausted all troubleshooting steps - fro ...

How to drag an item onto another element using Vue.Draggable without the need for adding or removing

Are you familiar with the library https://github.com/SortableJS/Vue.Draggable? I am trying to achieve a drag and drop functionality where I can drag a file into a folder. However, I am facing an issue as the @change event only provides data about the drag ...

What is the process for obtaining the dimensions (height/width) of a particular instance within a dynamically rendered list?

[QUESTION REVISED FOR CLARITY] I am attempting to retrieve the dimensions, specifically the width and height, of a rendered <div/> within the Child.js component. While I found a helpful example on Stack Overflow, my scenario involves multiple dynami ...

Transforming Thomas J Bradley's signature pad JSON into a PNG file using C# programming language

I have been experimenting with the Signature Pad plugin developed by Thomas J Bradley and successfully converted JSON signature to PNG using PHP. Now I am looking to achieve the same result using C#. There is a supplemental class called SignatureToImageDo ...

Interested in integrating server side JavaScript with your Android application?

Running a simple web page on a Raspberry Pi to toggle the board's LED with the click of a button. The button triggers a JavaScript function that controls the LED. Now, I want to be able to call this script from a button in an Android application... B ...

"Unfortunately, SimplyScroll isn't functioning properly on this particular page

What is the reason for simplyscroll not being able to handle this div with nested divs? EXTRA INFORMATION The scrolling functionality does not work. Clicking on the buttons doesn't trigger any scrolling. Changing the contents of the div to plain tex ...

(Freshman) Using ng-repeat and a for loop to retrieve information from an array of objects

My task involves accessing specific objects within an array: .success(function(data) { $scope.activities = data.result[0].attributes }); I can then display the data from this particular index in my view using <div ng-repeat="x in activities"&g ...

Navigating through the keys of a parameter that can assume one of three distinct interfaces in TypeScript: a guide

Here is a function example: function myFunc(input: A | B | C) { let key: keyof A | keyof B | keyof C; for(key in input) { let temp = input[key]; console.log(temp); } } The definitions for A, B, and C are as follows: interfa ...

Challenges with implementing speech recognition within a React component's state

I've encountered an issue with the React library react-speech-recognition. When trying to modify the newContent state within useEffect, it ends up printing as undefined. Additionally, I'm facing a similar problem when attempting to update the sta ...

Introducing a fresh parameter to initiate and end Server Sent Events

Assistance needed. I have implemented Server Sent Events to dynamically update a website using data from a database. Now, I aim to send a new parameter ('abc.php/?lastID=xxx') back to the PHP script based on information received in the previous ...

High RAM consumption with the jQuery Vegas plugin

I've been scouring the internet, but it seems like I'm the only one facing this issue. Currently, I'm working on a website that uses the jQuery vegas plugin. However, I noticed that if I leave the page open while testing and developing, my ...

Creating a switch statement that evaluates the id of $(this) element as a case

I have a menu bar with blocks inside a div. I am looking to create a jQuery script that changes the class of surrounding blocks in the menu when hovering over a specific one. My idea is to use a switch statement that checks the ID of $(this) and then modif ...