Sinon - a spy that can intercept *all* property accesses on an object

I have a function that looks something like this:

import {someLibraryMethod} from 'someLibrary';

function setFullWidth(el) {
  someLibraryMethod(el);
  el.setAttribute('width', '100%');
}

To test this function, I am utilizing Sinon to create a mock element that only supports the setAttribute method:

const fakeElement = {
  setAttribute: sinon.spy()
};
setFullWidth(fakeElement);
assert(fakeElement.setAttribute.calledWithExactly('width', '100%'));

An issue arises when someLibraryMethod assumes that el is an HTML element and attempts to invoke other methods on it, such as hasAttribute. While I could include these as stubs or spies in fakeElement, I am curious if Sinon offers a way to generate an object that can spy on any method or property access?

For instance:

const fakeElement = sinon.spyOnAnyProperty();
fakeElement.setAttribute(); // valid
fakeElement.hasAttribute(); // valid
fakeElement.foo(); // also valid
// some method to verify which methods were called

Answer №1

Utilize sinon.stub(obj) in order to

Mock all of the object's methods.

For example:

index.ts:

export function setFullWidth(el) {
  el.setAttribute('width', '100%');
  el.hasAttribute('width');
  el.foo();
}

index.spec.ts:

import { setFullWidth } from './';
import sinon, { SinonStubbedInstance } from 'sinon';
import { expect } from 'chai';

describe('58868361', () => {
  const fakeElement = {
    setAttribute(attr, value) {
      console.log('setAttribute');
    },
    hasAttribute(attr) {
      console.log('hasAttribute');
    },
    foo() {
      console.log('foo');
    },
  };
  afterEach(() => {
    sinon.restore();
  });
  it('should spy all methods of el', () => {
    const logSpy = sinon.spy(console, 'log');
    const stub: SinonStubbedInstance<typeof fakeElement> = sinon.stub(fakeElement);
    setFullWidth(fakeElement);
    expect(stub.setAttribute.calledWithExactly('width', '100%')).to.be.true;
    expect(stub.hasAttribute.calledWithExactly('width')).to.be.true;
    expect(stub.foo.calledOnce).to.be.true;
    expect(logSpy.callCount).to.be.equal(0);
    logSpy.restore();
  });

  it('should restore to original methods of el', () => {
    const logSpy = sinon.spy(console, 'log');
    setFullWidth(fakeElement);
    expect(logSpy.callCount).to.be.equal(3);
  });
});

Results of unit test with full coverage:

  58868361
    ✓ should spy all methods of el
setAttribute
hasAttribute
foo
    ✓ should restore to original methods of el


  2 passing (13ms)

---------------|----------|----------|----------|----------|-------------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files      |      100 |      100 |      100 |      100 |                   |
 index.spec.ts |      100 |      100 |      100 |      100 |                   |
 index.ts      |      100 |      100 |      100 |      100 |                   |
---------------|----------|----------|----------|----------|-------------------|

Code source: https://github.com/mrdulin/mongoose5.x-lab/tree/master/src/stackoverflow/58868361

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

Combining multiple AngularJS expressions to form a URL within an interpolation statement

While this explanation may be lengthy, I appreciate your patience as I try to articulate the issue at hand. The error I'm currently encountering is as follows: Error: [$interpolate:noconcat] Error while interpolating: Strict Contextual Escaping disa ...

Trigger controller redirection in AngularJS 1.5.0 by clicking on <tr> tag

Currently, I am facing an issue with my web page where I use the ng-repeat directive to populate a table with a list of companies. My goal is to have a functionality where clicking on a table row opens another page with a form to edit the selected company. ...

Creating customized npm package.json scripts for each individual console command

When running npm install <module> --save, the module is added to the package.json, which can be quite convenient. In the package.json, there is a section dedicated to scripts. It would be helpful to have a command that automatically adds scripts her ...

Simple guide on how to use AJAX (without jQuery) and PHP to count the number of records

As a novice programmer, I am attempting to tally the number of records in a table. Despite perusing various code snippets, I am unable to seamlessly integrate them to pass the PHP result to my javascript code. Here is the current state of my code: showsca ...

Ajax polling ceases the polling process when an internet connection is lost

My current setup involves a continuous ajax polling cycle where messages are pulled, processed, a wait period occurs, and then the process is repeated. Everything runs smoothly until a user disconnects from the internet, whether it be due to a simple actio ...

Automatically redirect users on page refresh using jQuery

I am attempting to use jQuery to detect when the user refreshes the page in order to redirect, but I can't seem to get it working. Can someone help me figure out what is wrong with this code? <?php include 'instagram.php'; ?> <! ...

When working with Visual Studio and a shared TypeScript library, you may encounter the error message TS6059 stating that the file is not under the 'rootDir'. The 'rootDir' is expected to contain all source files

In our current setup with Visual Studio 2017, we are working on two separate web projects that need to share some React components built with TypeScript. In addition, there are common JavaScript and CSS files that need to be shared. To achieve this, we hav ...

delivering axios response to display the page

I have a code snippet that requests data from an external API using axios and I want to incorporate the response into my rendered page. Here is my code: //Snippet from my controller required in main routes exports.recordBySlug = async (req, res, next) =&g ...

Save the HTML elements in an object and then use the ng-include directive to include the template

Currently experimenting with this code snippet: var CustomTable = function($elem) { this.properties.element = $elem; this.initialize(); }; CustomTable.prototype.PROPERTIE = { secondElement: $('.second') // this element ...

React Full Calendar Error: Unable to access property 'calendar' from undefined

I'm encountering an issue while attempting to save selected time information in state. Any assistance would be greatly appreciated, thank you for your help! Please feel free to ask if more specific details are required. Below is a snippet of code fro ...

Encountered an issue: Error message stating that a Handshake cannot be enqueued as another Handshake has already been enqueued

I am currently working on setting up a node.js server to handle POST requests that involve inserting data into two separate MySQL tables. Below is the code snippet for my node.js server: let mysql = require("mysql"); const http = require('h ...

Angular index.html file can include a conditional script

I am currently working on an Angular project, where the index.html serves as the main entry point for the application, just like in any other Angular project. This file contains important links and configurations. Within the HTML code snippet below, you w ...

Tips for styling Ajax.ActionLink elements with CSS

Hi there, I'm trying to style a navigation bar using CSS. I'm pulling the navigation bar values from a database using Ajax.ActionLink. I attempted to use JavaScript but encountered an error stating "jQuery is not defined", Here's the cod ...

Animating an element when another one fades away

const myVueInstance = new Vue({ el: '#app', data: { showBlueElement: true } }) .a, .b { height: 50px; width: 50px; background: red; } .b { background: blue; transition: transform 1s ease-in-out; } <script src="https://unpk ...

The z-index overlapping problem in webkit browsers is a result of Angular 7 animations

I have implemented staggered animations in my Angular 7 application to bring elements to life upon page load. However, I am facing a strange z-index problem with one of my components. Here is the animation code: @Component({ selector: 'track-page& ...

What is the method for setting a title within a for-loop when using vue.js in the given scenario?

<template v-for="item in job"> <tr> <td v-for="i in item.stage" :style="getStyle(i.status.name)" title="[[ i.node.name ]]" > <b>[[ i.node.name ]]</b> </td> </tr> </template> ...

I plan to compile a collection of names in a text field and then have the ability to select and access each name individually with just a click

I am currently working on a project that involves creating an admin site using Firebase. One of the main features of the site is large text fields that display information which can be modified. For instance, the user management page includes text fields ...

What is the best way to implement automation for this task using Selenium WebDriver?

As a newcomer to Selenium Webdriver (Js), I am eager to learn the basics but have hit a roadblock with a specific example: My current challenge is automating a Google search for "Diablo 2 Resurrected days until release" and then displaying the remaining d ...

The $formatters directive plays a role in modifying the ngModel as it is

I am facing an issue with using $formatters. My objective is to conceal the phone number, while displaying only the last 4 characters. It should not display anything if left blank. If text is entered, the model is impacted by the mask and the hidden phone ...

When a single piece of content is exposed, it has the power to bring down all

I've been working with some code where clicking on a specific button toggles the visibility of certain contents. It's functionality is satisfactory, but I want to take it a step further. In addition to showing and hiding content, I also want to e ...