Jasmine - effectively mimicking an object that utilizes a constructor

Currently, I am attempting to simulate the native WebSocket in a jasmine test for Angular. I have successfully spied on the constructor and `send` function, but I am struggling to figure out how to fake a call of `onmessage`.

The WebSocket has been extracted to an Angular constant named `webSocket`.

This is what my test setup looks like:

describe('Data Service', function () {

  var dataService,
    $interval,
    ws;

  beforeEach(function () {
    module('core', function ($provide) {
      ws = jasmine.createSpy('constructor');
      ws.receiveMessage = function (message) {
        this.onmessage(message);
      };
      $provide.constant('webSocket', ws);
    });

    inject(function (_dataService_, _$interval_) {
      dataService = _dataService_;
      $interval = _$interval_;
    });
  });

  it("should call subscribers when a message is received", function () {
    var callback = jasmine.createSpy('onMessage callback');

    function message(type) {
      return {
        data: {
          type: type,
          data: 'data'
        }
      };
    }

    // Subscribe to messages via the exposed function.
    // Subscribe to one of them twice to test that all subscribers are called and not just the first one.
    dataService.onMessage(21, callback);
    dataService.onMessage(21, callback);
    dataService.onMessage(22, callback);
    dataService.onMessage(23, callback);

    // Pick 3 numbers that are valid data types to test.
    ws.receiveMessage(message(21));
    ws.receiveMessage(message(22));
    ws.receiveMessage(message(23));

    expect(callback.calls.count()).toBe(4);
    expect(callback.calls.allArgs()).toBe([message(21).data, message(21).data, message(22).data, message(23).data]);
  });
});

This is the code snippet that I am working with:

angular.module('core', []).constant('webSocket', WebSocket);

angular.module('core').factory('dataService', function ($interval, webSocket) {

  function openSocket() {
    sock = new webSocket('ws://localhost:9988');

    sock.onmessage = function (message) {
      var json = JSON.parse(message.data);

      onMessageSubscribers[json.type].forEach(function (sub) {
        sub(json);
      });
    };
  }

  function onMessage(type, func) {
    onMessageSubscribers[type].push(func);
  }

  openSocket();

  return {
    onMessage: onMessage
  };
});

The array `onMessageSubscribers` contains the correct types as keys, but this is irrelevant to the issue at hand.

When running the test, I encounter the following error:

TypeError: 'undefined' is not a function (evaluating 'this.onmessage(message)')

`onmessage` seems to be defined by the angular code that executes before the test, however, I suspect there might be something related to how a constructed object differs from a regular object in JavaScript, which I do not have much experience with.

I have experimented with various approaches, such as calling `ws.prototype.onmessage` or simply `ws.onmessage`.

If I insert a `console.log(sock.onmessage);` in `dataService` at the appropriate location, and another log before invoking `onmessage` in the tests:

function (message) { ... }

undefined

How can I trigger a call to onmessage or any other WebSocket event?

Answer №1

Creating a new instance by calling var sock = new webSocket(); invokes the webSocket constructor to generate the object. However, due to the design implementation, the sock variable (instance of webSocket) is not publicly accessible and therefore cannot be used in a test suite. Subsequently, when you set the onmessage property on this sock instance:

sock.onmessage = function () { ... }
.

The challenge arises when you wish to manually trigger the onmessage event from within the test suite. Since the onmessage function is linked to the sock instance which is inaccessible in the test suite, accessing onmessage becomes problematic.

Even attempting to access it through the prototype chain proves futile without an actual reference to the sock instance.

To overcome this hurdle, a clever workaround was devised.

In JavaScript, there exists a peculiar capability where an object can be explicitly returned from a constructor (more information available here), directly assigning it to a variable instead of generating a new instance. For example:

function SomeClass() { return { foo: 'bar' }; }
var a = new SomeClass();
a; // { foo : 'bar' }; }

Here is how this concept can be utilized to access the onmessage:

var ws,
    injectedWs;

beforeEach(function () {
    module('core', function ($provide) {

        // An object is explicitly returned
        injectedWs = {};

        // This acts as a constructor - replacing the native WebSocket
        ws = function () {
            // Explicitly return the object
            // It will then be assigned to the 'sock' variable
            return injectedWs;
        };

        $provide.constant('webSocket', ws);
});

Consequently, when setting sock.onmessage = function () {}; in the DataService, it is actually being assigned to the explicitly returned injectedWs. This allows accessibility in the test suite due to another interesting aspect of JavaScript - objects are passed by reference.

By implementing this solution, you can now invoke onmessage within the test environment whenever necessary using injectedWs.onmessage(message).

All relevant code has been relocated to: http://plnkr.co/edit/UIQtLJTyI6sBmAwBpJS7.

Side note: Certain issues with test suite expectations and JSON parsing may have occurred since access to this code was limited previously, modifications have been made to facilitate smooth execution of tests.

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

Text alignment issues cause animation to vanish

Utilizing particles.js, I set up a full-screen particle effect by specifying the animation to be full-screen with height: 100vh;. This worked perfectly as intended. However, when attempting to add text on top of the particle animation and center it vertica ...

Content that is set to a fixed position within a hidden element will remain at the top

translate3d(0%, 0px, 0px); is causing issues with my position fixed element. In my demo, you'll notice that clicking the button should open up the content just fine, but it is supposed to stay fixed at the top in a position fixed. Therefore, when scr ...

Using CSS to style the footer with a body height set to 100%

I am currently working on an angularJS web app with a basic template structure: <html> <head> <link rel="stylesheet" href="style.css" /> </head> <body id="top"> <!-- Templates with vi ...

Can anyone suggest a method to block the execution of Javascript commands in the console?

Regarding the inquiry mentioned in the question title. Is it possible to prevent individuals from executing functions on my webpage using the console, even though it is not necessary? I have experimented with various methods, and the code below represent ...

Using Node.js to update information within Firebase

Here's a problem I'm facing: I have a cron job running in Node.js that sends data to a Firebase database every minute. The issue is, even when there are no changes, the database still receives information. Take a look at my code snippet below: l ...

Tips for achieving proper styling and formatting of elements in jQuery UI

I've encountered an issue when trying to use jQuery UI after downloading it multiple times. In the examples provided with the download, the UI elements appear perfectly formatted, but when I implement them on my own pages, the styles and formatting ge ...

Transforming a REST API get call into GraphQL

I'm currently using a REST API that accepts a code parameter, searches the database for matches, returns results if the parameter exists, and redirects users to a long_url retrieved from the database. How can I convert this functionality to GraphQL? i ...

Is there a way to handle templates in AngularJS that is reminiscent of Handlebars?

Is there a way to handle an AngularJS template using a syntax similar to Handlebar? <script type="text/ng-template" id="mytemplate"> Name is {{name}} </script> I know how to retrieve the template using $templateCache.get('mytemplate&ap ...

Access a specific element within an array using Handlebars.js

After converting a CSV to JSON, I have data that looks like this: [["Year","Make","Model","Description","Price"],["1997","Ford","E350","ac, abs, moon","3000.00"],["1999","Chevy","Venture \"Extended Edition\"","","4900.00"],["1999","Chevy","Ventu ...

Is it possible to utilize Python and WebDriver to Assert/Verify the Presence of an Element

I'm a bit confused by the transition from Selenium to WebDriver and the differences in their respective documentation. The documentation mentions using Assert vs Verify, like AssertElementPresent, when discussing test design considerations. However, a ...

Is there a way to convert this asynchronous function into a synchronous one so that it returns the value immediately

When it comes to making a Nodejs/Javascript method synchronous, there are several solutions offered by the community. Some suggest using libraries like async and fibrous, but these involve wrapping functions externally. However, I am in search of a soluti ...

What is the proper way to reference the array you require within a function?

Is there a way to input the name of an array (such as a, b, or any other chosen array) into a function? I've tried using inputs[3].value but it doesn't seem to be working. How can I achieve this? I have a total of 4 fields, and I think using sele ...

Jest is having trouble locating numerous files within the __tests__ directory

I'm facing an issue while trying to use jest with the vue.js framework. When running yarn test:unit (e.g. vue-cli-service test:unit), only the last file in the tests folder is being recognized, even though there are several files present. I have tried ...

Unable to trigger onSelect event on the datepicker component

Whenever a date is chosen, I need to trigger a javascript function. Here is the javascript code: $(document).ready(function(){ var date_input=$('input[name="date"]'); //our date input has the name "date" var container=$('.bootstrap- ...

What is the best way to send data to a component through a slot?

Currently, I am working on a Vue application that utilizes pimcore and twig in the backend. My task involves creating a component that can receive another component (slot) and dynamically render it with props. Here is the root structure found in viani.twig ...

"Utilizing Angular for Select Elements and ng-Option Bindings

I am facing an issue with getting ng-option to work properly. I have a variable alarm.type and I need to create a dropdown list of options derived from $scope.weekTypes. Here is what I have attempted so far: $scope.weekTypes = ["null", "sunday", ...

Invoke the REST API and save the compressed file onto the local machine

This is the code snippet I currently have: jQuery.ajax({ type: "GET", url: "http://localhost:8081/myservicethatcontainsazipfile", contentType:'application/zip', success: function (response) { console.log("Successful ...

Guide on utilizing the <source> tag within v-img component in Vuetify.js?

When it comes to using webp images instead of jpg or png, some browsers may not support the webp format. In such cases, we can use the html tag < source > as demonstrated below, ensuring that at least a jpg image is displayed: <picture> < ...

Animated SVG Arrow Design

I created a dynamic SVG animation that grows as you hover over it. Since I'm still learning about SVG animations, I encountered some issues with my implementation. The animation is quite straightforward - when hovering over the SVG arrow, the line sho ...

Creating a tree structure in JavaScript by parsing a collection of URLs

Hello everyone, I am currently working on creating a dynamic menu list that allows users to create elements without any depth limitations. THE ISSUE The structure of my URLs is as follows: var json_data = [ { "title" : "Food", "path" ...