Checking the status of an object using a Jasmine spy call in an Ajax method

Currently, I am unit testing an Angular controller that relies on a Rails Resource factory to manage data exchange with a Rails application. Specifically, POST requests are handled by calling a method on the model, like so:

$scope.resource.update().then(successHandler, failureHandler);

To facilitate unit testing of the controller, I have set up a spy on this method in order to simulate Ajax calls:

resUpdateSpy = spyOn($scope.resource, 'update').and.callFake(function() { 
  return {then: function(success, failure){ success(resUpdateResponse); }};
});

During testing, there is a need to ensure that certain data (such as Stripe information) is being POSTed with the resource. However, since the data is modified after the POST operation within the same method, traditional testing methods prove challenging. Ideally, I would like to validate the state of the resource during the POST using something like:

expect($scope.resource.update).toHaveBeenCalled().whileValueOf($scope.resource.stripeKey).isEqualTo('tok123');

Unfortunately, such a method is not available in standard Jasmine testing. Is there a way, possibly through vanilla Jasmine or a third-party extension, to examine the value of a property when a specific spy is triggered? Alternatively, is there another approach to testing this scenario – focusing on the state of the model prior to its data being sent out for POSTing?

My setup includes Jasmine 2.2.0 along with Teaspoon 1.0.2 integrated into an Angular 1.3.14 application.

Answer №1

If you want to create your own custom jasmine matchers, one useful one could be:

jasmine.Matchers.prototype.toBeResolvedWith = function() {
  var done, expectedArgs;
  expectedArgs = jasmine.util.argsToArray(arguments);
  if (!this.actual.done) {
    throw new Error('Expected a promise, but got ' + jasmine.pp(this.actual) + '.');
  }
  done = jasmine.createSpy('done');
  this.actual.done(done);
  this.message = function() {
    if (done.callCount === 0) {
      return ["Expected spy " + done.identity + " to have been resolved with " + jasmine.pp(expectedArgs) + " but it was never resolved.", "Expected spy " + done.identity + " not to have been resolved with " + jasmine.pp(expectedArgs) + " but it was."];
    } else {
      return ["Expected spy " + done.identity + " to have been resolved with " + jasmine.pp(expectedArgs) + " but was resolved with " + jasmine.pp(done.argsForCall), "Expected spy " + done.identity + " not to have been resolved with " + jasmine.pp(expectedArgs) + " but was resolved with " + jasmine.pp(done.argsForCall)];
    }
  };
  return this.env.contains_(done.argsForCall, expectedArgs);
};

You can find more custom matchers here.

After taking into account the comment:

Jasmine actually supports creating your own custom matchers. While it already includes built-in ones like toBe and toEqual, you can add your custom matcher to specifically test promises.

var customMatchers = {

toHaveBeenResolved: function(util, customEqualityTesters){
  return {
    compare: function(actual, expected){
      var result = {};
      // Add your comparison logic here
      result.pass = true/false;
      result.message = 'Some message about the test result';
      return result;
    }
}

Your this.actual is a promise that can be resolved like this:

result = {};
promise.then(function(value){
  result.value = value;
  result.status = 'Resolved';
}, function(value){
  result.value = value;
  result.status = 'Rejected';
});

Once you've defined your custom matcher, make sure to use it in your test case within the beforeEach callback.

beforeEach(function(){
  jasmine.addMatchers(customMatchers);
}); 

Answer №2

To utilize your spy for forwarding calls to a fabricated function, you can have that fabricated function save the desired value for later use:

let storedValueDuringSpyCall = '(spy not yet invoked)';
resUpdateSpy = spyOn($scope.resource, 'update').and.callFake(function() {
  storedValueDuringSpyCall = $scope.resource.stripeKey;
  return {then: function(success, failure){ success(resUpdateResponse); }};
});

Subsequently, you can verify the saved value in your assertion:

expect(storedValueDuringSpyCall).toEqual('tok123');

(Depending on the scope of both the spy setup and assertion, you may need to adjust where the variable is stored.)

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

subscribing to multiple observables, such as an observable being nested within another observable related to HTTP requests

Hello, I recently started learning Angular and I am facing a challenge with posting and getting data at the same time. I am currently using the map function and subscribing to the observable while also having an outer observable subscribed in my component. ...

What could be causing my tabs (such as HOME, ABOUT ME..) not displaying the correct paragraph or section content?

I have set up navigation tabs on my website using anchor tags, but they are currently not linked to any specific paragraphs. I want the corresponding paragraph to be displayed when a tab is clicked, but I'm unsure how to add this functionality using j ...

Activating the onclick function to open a tab and automatically position the cursor inside a textfield

I have a requirement to automatically place the cursor on a specific field whenever a rich tab is navigated to. I attempted using onclick and onlabelclick on the richTab, but it did not work as expected. Then, I tried utilizing ontabenter, which called my ...

What modifications need to be made to the MEAN app before it can be deployed on the server?

Following a tutorial on Coursetro, I was able to successfully build an Angular 4 MEAN stack application. However, when it comes to deploying the app on a server running on Debian-based OS, I am facing some challenges. The application should be accessible o ...

"jquery-ajax was unable to complete the request, whereas it was successfully executed using

I am currently using jQuery to trigger an ajax request. However, when I make the request using jQuery, I encounter an "Unexpected end of input" error with no response coming from the PHP file. Strangely enough, if I manually copy the request from the Chrom ...

Disparity in response status codes during an ajax call from both client and server

Scenario: - Node/Express/Angular 1.x Issue - The client always receives a response code of 200 over the ajax call, even when the server response headers indicate 304 or 200 (confirmed in the server console and browser network response headers). What is th ...

Learn how to import MarkerClusterer from the React Google Maps library without using the require method

The sample provided utilizes const { MarkerClusterer } = require("react-google-maps/lib/components/addons/MarkerClusterer");, however, I would prefer to use the import method. I attempted using: import { MarkerClusterer } from 'react-google-maps/lib ...

IE8 experiencing issues with jQuery live click event functionality

I have this code snippet that is working perfectly fine in all browsers except for IE8. The #cal_popup_table element is dynamically added to the page. $("#cal_popup_table tbody tr td a").live('click', function() { $('.da ...

The Javascript code is functioning properly in Chrome, however, it is experiencing compatibility issues in other

Recently, I put together a simple web page using React and Express. One of the modules features a basic form with text input fields, an email input field, and a submit button that is supposed to send an email containing data from the input fields to me. To ...

Unit Testing JWT in Angular 2

Using JWT for authentication in my API calls. I am in the process of coding a service method. An interceptor is applied to all requests: public interceptBefore(request: InterceptedRequest): InterceptedRequest { // Modify or obtain information from ...

What is the correct way to test setInterval() statements within Angular?

Here is a simple code snippet I am working on: public async authenticate(username: string, password: string) { const authenticationResponse = await this.dataProvider.authenticate(username, password); if (authenticationResponse.result.code == 0) { ...

Incorporating dynamic data binding using AngularJS in a span tag

In my view, I am using this Angular expression: <span ng-if="{{list.StoreList ? (list.StoreList.length ' Products)' : '(0 Products)'}}"> </span> The purpose is to display the count of items in StoreList if there are any, ...

Implementing a versatile free text filter with numerous values using MUI

I am currently working on incorporating a free text filter view using MUI. Although the Autocomplete component already has this feature (when the 'multiple' attribute is enabled), I am looking to input free-form text instead of selecting from pre ...

What is the best way to retrieve JSON key/value pairs instead of an array?

I am working on retrieving data from a Google Spreadsheet using App Script and have set up a DoGet function. Currently, I am getting an array of data but I need it in JSON key-value pairs format. The table in my Google Sheets is structured as follows: Th ...

Refresh an Angular page automatically

Having a small issue in my angular application. The problem arises on the first page where I display a table listing all employees along with a "Create New Employee" button that opens a form for adding a new employee. However, after submitting the form and ...

Choose the option in real-time with Jquery

I'm currently developing a dynamic HTML for Select Option as seen below: item += "<td class='ddl' style='width:40%;'>"; item += "<select>" item += " <option id='list' name='selector' value=" + se ...

The JSON response from XHR begins with the word "for."

While examining a few Facebook XHR requests, I noticed that there is a cross-domain request with a JSON response that begins like this: for (;;); {/* JSON object */} Why does the response start with a for? I suspect it might be for security reasons. Can ...

Is it possible to simultaneously use two $scoped variables within an Angular controller?

Currently, I am developing an angular application that connects to a Rails backend and interacts with the database through API calls to receive JSON objects. My challenge lies in defining multiple scoped variables within a controller. At the moment, I have ...

When restarting the application, values stored in cookies are not retained in ElectronJS paired with AngularJS

Currently, I am diving into mastering ElectronJs. To kickstart my learning journey, I decided to create a basic AngularJs application with it, but encountered some hurdles along the way. I have a requirement to store persistent data such as user tokens an ...

How to effectively execute AJAX requests on the server side using Node.js

I am currently in the process of developing a search engine that utilizes both Twitter and Wiki APIs on the server-side after receiving a search query from the client. Previously, when operating solely on the client-side, my AJAX request to the Wiki API lo ...