Jasmine's capability to test method logic that executes following the resolution of a returned promise

I am looking to test a JavaScript method that I have written. Here is a simplified pseudo code representation of it:

$scope.savePerson = function(){
      Person.create($scope.person).then(function(newPerson){
          if($scope.person.org){
             Organization.addPerson($scope.person.org, newPerson.id);
          }

     toaster.pop('success', "person added");
}

Although this sample uses restangular, the concept behind it is straightforward. The savePerson method saves the person object first and then attempts to save the person to an organization if an organization is specified. It is important that the organization cannot be saved until the person has been.

To test this logic, I plan on creating spies for both Person and Organization, and then verifying that they are both being called. A common approach may resemble the following:

describe( 'save person', function () {
var Person, Organization, person;

beforeEach(inject(function($controller, $q, _Organization_, _Person_) {
   Person=_Person_;
   Organization=_Organization_;
   person={...} //whatever data is needed, including an organization

   Person.create=jasmine.createSpy('create()').andCallFake( funciton(newPerson){
       var defer = $q.defer();
       defer.resolve(personParam);
       return defer.promise;
  });

  Organization.addPerson= jasmine.createSpy('addPerson()').andCallFake(funciton(personParam){
     var defer=$q.defer();
     defer.resolve(personParam);
     return defer.promise;
  });

 controllerOptions.Person=Person;
 controllerOptions.Organization=Organization;
 controllerOptions.person=person;
 MyController= $controller('MyController', controllerOptions);
}));

it('adds org if exists', function(){
   $scope.savePerson($scope.person);

  expect(Person.create).toHaveBeenCalled();
  expect(Organization.addPerson).toHaveBeenCalled();
});

However, currently this will fail, specifically because addPerson will not be called. This is not a flaw in the code itself but rather an issue with threading. Due to using .then on Person.create, an asynchronous thread is created. As a result, Organization.addPerson will not execute until the async thread triggers and runs the then logic. The problem lies in the fact that the test proceeds without waiting for that thread to complete, leading to failures when reaching the expectations.

One quick but lazy solution would involve adding a short 10 millisecond wait in the test. By doing so, the async thread will start running immediately upon waiting, allowing addPerson to be executed promptly. However, relying on timing assumptions is not rigorous enough for testing purposes.

An alternative approach could involve having the organization's addPerson function set some "addPersonCalled" value and then utilizing async calls that only run once addPersonCalled is triggered, though this may appear cumbersome.

This dilemma seems like a common scenario. I am curious if Jasmine offers a more elegant solution for handling such cases? Is there a way to instruct Jasmine to wait for any current .then methods invoked on promises to resolve before proceeding with a test?

Answer №1

When faced with two options, we had to decide how to handle resolving promises in our testing. The first option was to use either $timeout.flush() or $rootScope.$apply(), which would resolve all promises (Testing AngularJS promises in Jasmine 2.0)

However, this approach caused problems because it resolved all promises, including ones we weren't ready to resolve yet due to workarounds for legacy issues that we were in the process of removing entirely.

We also came across another interesting tool called Mock-promises:

Although we haven't had the opportunity to try out this tool yet, it seems like a promising (pun intended!) solution to explore further.

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

Tips on saving checklist values as an array within an object using AngularJS

I need help with storing selected checklist items as an array in a separate object. I want to only store the names of the checklist items, but I am struggling to figure out how to achieve this. Below is the HTML code: <div ng-app="editorApp" ng-contro ...

Do GPU-intensive animations get impacted by the CPU's load?

I have set up a special compositing layer for a div with the following unique styles: div { position: absolute; height: 50px; width: 50px; background: #900; top: 100px; left: 200px; will-change: transform; transform: translateZ(0); } Afte ...

What is the best way to upload a file to Firebase Storage using React?

I am facing difficulty uploading a file to Firebase in order to retrieve its getDownloadURL. This is the code snippet I currently have: import React, {useState, useEffect} from 'react' import { Container, Button, Row, Col, Form, Alert } from &ap ...

Obtain asynchronous state in VueJS without the need for intricate v-if conditions

I am working on an application that heavily relies on Vue-Router and Vuex for state management. Within the Dashboard component, important user information is displayed. This data is fetched asynchronously from a database by Vue and then stored in Vuex. T ...

Express.js experiencing issues with updating route file

Having recently started using both Node and Express, I was excited about the potential of Express.js. As a result, I set up a local deployment with a basic installation. When I visited http://localhost:3000/users, I was greeted with the message: respond ...

Managing traffic in Google Kubernetes Engine (GKE)

I am encountering an issue with our website deployment on GKE, which consists of 10 pods. When deploying a new version, we use MAXsurge=1 and MAXunavailable=0. Upon trying to access the website during a new deployment, I sometimes only see the header in t ...

Invoke a function within an HTML element inserted using the html() method

Looking for help with a JavaScript function: function toggle_concessions(concessions) { var text = "<table>"+ "<tr><td class='concession-name'>gfhgfbfghfd</td><td class='op-encours&a ...

HAproxy: unique error handling for OPTIONS and POST requests with 503 errorfile

Our Web application utilizes ajax calls to a backend that operates on a different domain, requiring CORS. The backend setup includes an HAproxy 1.4.22 along with multiple Wildflys running on the OpenShift PaaS. During times when a Wildfly instance is unava ...

Updating state based on input from a different component

I am attempting to modify the state of the page index in index.js from the Pagination component, Here is my index.js code: import useSWR from 'swr'; import { useState } from 'react'; const Index = ({ data }) => { const ini ...

Updating a Vue component upon resolution of a promise and effectively passing props to its nested children

I have a scenario where I need to pass data from a parent component to a child component as props. The parent component's data is fetched via an ajax call. I have tried a couple of solutions, but they are not working as expected. Can you help me iden ...

Waiting for a Node.js/JavaScript module to finish running before proceeding

I've developed an express.js application that integrates with MongoDB using mongoose. Currently, I have the mongoose connection code stored in a separate file since it's used across multiple modules frequently. Below is the connector code: con ...

What is the correct way to outline the parameters for deactivating functions?

Request for Assistance! I am facing a challenge with multiple blocks in my WordPress website. Each block contains a checkbox, two select options, and an element that needs to be toggled based on the selected options in the dropdowns. The functionality to ...

Wrapping a group of elements with opening and closing tags using jQuery

I have a collection of distinct elements, like so: <section class="box-1"></section> <section class="box-2"></section> <section class="box-3"></section> My objective is to enclose all three elements within a div with a ...

AngularJS refresh the display of ngRepeat items

I'm currently displaying products and their details using ngRepeat in a table. In addition to that, I have created a custom directive: .directive('onFinishRenderFilters', function ($timeout) { return { restrict: 'A', ...

What is the best way to create a line break in text?

I've recently started familiarizing myself with Angular Material and I'm curious about how to display multiple cards side by side. I've been using ng-repeat for this, but I would like a maximum of three cards per row before a new row starts ...

javascript download multiple PDF files on Internet Explorer

I am facing an issue with downloading multiple PDF files In my list, I have various a elements with links to different PDF files. I created a loop to go through each a element and generate an iframe using the value of the href as the source. This solutio ...

What is the process for mapping a texture or shape to a particular section of a mesh in Three.js?

Is there a way to project a texture or shape onto a specific part of a mesh, such as a transparent ring or circle? I want to achieve a similar effect to what we see in games when selecting an enemy or NPC, where a circle appears under the character to indi ...

Tips for displaying just one dropdown when selecting an option in a jQuery menu

My menu is almost perfect, but I am facing one issue. When I click on .dropdown-toggle, only the closest ul should show, but in my code, all of them are shown and hidden simultaneously. What I want is that when I click on .dropdown-toggle, only the next ul ...

Is it possible to showcase two modals on a single page, positioning one to the left and the other to the right using Bootstrap?

Can someone help me learn how to display two modals on the same screen, one on the left and one on the right? I am new to bootstrap and would appreciate some guidance! ...

Error: Unable to iterate over JSON data as json.forEach is not a valid function

let schoolData = { "name": "naam", "schools" : [ "silver stone" , "woodlands stone" , "patthar" ], "class" : 12 } schoolJSON = JSON.stringify(sc ...