Exploring Angular controllers, promises, and testing

Currently, I am in the process of writing some unit tests for my controller that utilizes promises. The code snippet in question is as follows:

UserService.getUser($routeParams.contactId).then(function (data) {
      $scope.$apply(function () {
      $scope.contacts = data;
   });
});

In order to properly test this functionality, I have created a mock for my UserService and here is my unit test:

beforeEach(inject(function ($rootScope, $controller, $q, $routeParams) {
        $routeParams.contactId = contactId;
        window.localStorage.clear();
        UserService = {
            getUser: function () {
                def = $q.defer();
                return def.promise;
            }
         };
        spyOn(UserService, 'getUser').andCallThrough();


      scope = $rootScope.$new();
      ctrl = $controller('ContactDetailController', {
        $scope: scope,
        UserService:UserService
      });
  }));


it('expects to receive 1 contact', function () {
    expect(scope.contacts).not.toBeDefined();
    def.resolve(contact);
    scope.$apply();

    expect(scope.contacts.surname).toEqual('NAME');
    expect(scope.contacts.email).toEqual('EMAIL');
});

However, when running this test, it results in the following error message:

Error: [$rootScope:inprog] $digest already in progress

When removing the $scope.$apply from the controller, the test passes, but the controller's functionality is compromised:

UserService.getUser($routeParams.contactId).then(function (data) {
    $scope.contacts = data;     
 });

So, I am currently seeking advice on how to handle this situation moving forward. Any suggestions would be greatly appreciated.

Based on replies received, it has been pointed out that the $apply is not occurring in the UserService, but rather within the controller itself. Here is an updated snippet showcasing where the $apply is being utilized:

EDIT: The $apply block is implemented in the controller in this manner:

appController.controller('ContactDetailController', function ($scope, $routeParams, UserService) {
    UserService.getUser($routeParams.contactId).then(function (data) {
      $scope.$apply(function () {
        $scope.contacts = data;
    });
});

Here is the implementation of the actual UserService:

 function getUser(user) {
    if (user === undefined) {
      user = getUserId();
    }
    var deferred = Q.defer();
    $http({
      method: 'GET',
      url: BASE_URL + '/users/' + user
    }).success(function (user) {
      deferred.resolve(user);
    });
    return deferred.promise;
}

Answer №1

Your UserService has a few issues that need to be addressed.

  • Instead of using Q, you should be using $q in Angular, as this is the standard practice and may impact when callbacks are executed.

  • In the getUser function, you are unnecessarily creating a promise, which can be considered an anti-pattern. It's recommended to avoid using the success function of the $http promise and stick with the standard then function for better chaining:

    function getUser(user) {
      if (user === undefined) {
        user = getUserId();
      }
      return $http({
        method: 'GET',
        url: BASE_URL + '/users/' + user
      }).then(function(response) {
        return response.data;
      });
    }
    

Update the controller code once the above changes have been implemented:

UserService.getUser($routeParams.contactId).then(function (data) {
  $scope.contacts = data;     
});

Make sure to call $apply after resolving the promise during testing.

def.resolve(contact);
scope.$apply();

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

Eliminate the need for jQuery when performing basic text rotation operations

Currently, I find myself loading an entire jQuery library for a task that could be achieved with much simpler code. The task involves cycling through a list of text spans to display different messages each time. Below is the existing code: (function($) ...

Phonegap Application: Page design gets distorted following keyboard input

After a user enters login details and the next page loads, the layout shifts as shown in the image below. The entire app layout breaks and the view moves down to accommodate the size of the onscreen keyboard. This issue persists unless the orientation is ...

What is the best way to guide the user to a different page with PHP once the preventDefault() function in jQuery has been utilized on a

My approach to form validation involves using jQuery, AJAX, and PHP on my website. I utilize PHP for the actual input validation process as I believe it is more secure and prevents users from manipulating scripts through browser source code inspection. Add ...

Execute Javascript to close the current window

When I have a link in page_a.php that opens in a new tab using target="_blank". <a href="page_b.php" target="_blank">Open Page B</a> In page B, there's a script to automatically close the tab/window when the user is no longer viewing it: ...

Is there a way to execute a node script via command line sans the need for installation and external packages?

Is there a way to execute a node script from the command line during development without actually installing anything, but still having access to installed packages using npm install <...>? When I try node ./bin/my_script.js, the script does not reco ...

Creating a material texture with file api in three.js is a straightforward process

I have a vision to create a cutting-edge model browser that allows users to handpick models and textures themselves. In order to achieve this, I am utilizing the File API for smooth file handling. My approach involves using two separate file inputs for rec ...

How can I fix the issue of clearInterval not functioning properly in an Electron JS application?

The clearInterval function is not working properly in this code. What can be done to fix this issue? var inter; ipcMain.on("start-stop",(err,data)=>{ console.log(data.data) function start(){ inter = setInterval(fu ...

Issue with nested views in Angular UI-Router not displaying properly

The issue I'm facing is that the template <h1>HELLO</h1> is not loading into the nested ui-view in analysis.client.view.html. However, the ui-view in the analysis.client.view.html file is being loaded successfully. I've tried naming t ...

Passing data between child components using Vuejs 3.2 for seamless communication within the application

In my chess application, I have a total of 3 components: 1 parent component and 2 child components. The first child component, called Board, is responsible for updating the move and FEN (chess notation). const emit = defineEmits(['fen', 'm ...

Displaying content on the <div> element

Looking for recommendations for a jQuery plugin or JavaScript solution that allows me to load a full "view" into a <div> when a user clicks on a link. The challenge I'm facing is that I have 8 pages, with the Homepage consisting of 3 divisions: ...

The function window.open has been disabled on codepen.io site

My dilemma involves a button designed to open a random Wikipedia page. While the code works perfectly in the CodePen editor, I encounter issues when opening it in full-page view. The problem arises when the log displays 'window.open is disabled'. ...

Combining Multiple Values from Various Form Elements using Jquery's .sum() Method

Below is the form provided for calculation purposes... <form> <label>First:</label> <select class="first"> <option value="0">Earth</option> <option value="1">Mars</option> <option value="2 ...

Formik button starts off with enabled state at the beginning

My current setup involves using Formik validation to disable a button if the validation schema is not met, specifically for a phone number input where typing alphabets results in the button being disabled. However, I encountered an issue where initially, ...

Is it a Mozilla Firefox glitch or something else?

After writing the code, I noticed a bug in Firefox and a small bug in Chrome. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> ...

Why does JSON remain unchanged when a value is explicitly assigned in Javascript

Why isn't my JSON structure updating when I explicitly assign a new value? items[0][i]['human_addressItem'] = address; I am trying to reverse geocode the latitude and longitude to obtain the human address, which is working fine. However, I ...

AngularJS UI-Router in hybrid mode fails to recognize routes upon initial page load or reload

It appears that when using the @ui-router/angular-hybrid, routes registered within an ng2+ module are not being recognized during the initial load or reload. However, these same routes work fine when accessed by directly typing the URL. I have followed th ...

The `process` variable is not recognized in a Vue/TypeScript component

I've encountered an issue trying to use .env variables in my Vue app. When I ran npm install process, the only syntax that didn't result in an error when importing was: import * as process from 'process'; Prior to this, I received the ...

What sets apart defining a function in ReactJS using only parentheses versus curly braces within parentheses?

As a newcomer to React, I encountered an interesting application that had functions defined in two different ways. One style was async function (a, b) => {//body}, which I found easy to understand. However, another set of functions followed the struct ...

Transform JavaScript into Native Code using V8 Compiler

Can the amazing capabilities of Google's V8 Engine truly transform JavaScript into Native Code, store it as a binary file, and run it seamlessly within my software environment, across all machines? ...

How can AngularJS display ellipses using the limitTo filter exclusively for strings that surpass the character limit?

Looking to use the limitTo filter on strings? If a string is under 10 characters, it will be displayed as is. But if it's longer than 10 characters, I need to cut it off at the tenth character and display ellipses. Is there an easy way to do this with ...