npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

using-stubs

v2.0.1

Published

Deep code stubbing for node.js tests. Follow and override behaviour of infinitely nested pieces of code such as methods, require() and npm modules or even instances of classes, etc.

Downloads

22

Readme

#using-stubs using-stubs NPM package information

using-stubs travis-CI build using-stubs test coverage

Stubbing and verification for node.js tests. Enables you to validate and override behaviour of nested pieces of code such as methods, require() and npm modules or even instances of classes.

This library is inspired on node-gently, MockJS and mock-require.


Getting started

var using = require('using-stubs')(); //an instance of using

Matching, following and stubbing in-depth code

using(object, 'method').like( foo.method("example") );

using(object, 'method').stub( function(originalFn, args, context){ /*...*/ } );

using(object, 'method').follow( function(resultingObject){ /*...*/ } );

Consider the object:

//class Baz
function ClassExample(){
  this.test = function(){
    return "";
  }
}

//object foo
var foo = {
  bar: function(){
    return {
      Baz: ClassExample
    }
  },
  getBaz: function(){
    return ClassExample
  }
}

Basics: Dive deep into Baz class, intercept and modify returned instance:

//setup using()
using(foo, 'bar').follow(function(obj){

  return using(obj, 'Baz'); //chaining with .follow()

}).follow(function(bazInstance){

  // modify bazInstance however you'd like
  using(bazInstance, 'test').stub(function(){
    return "OK!";
  })

});

//actual test case
var obj = foo.bar();

var instance = new obj.Baz();

console.log(instance.test()); //prints "OK!"

Class constructor: Actually replace bazInstance with your mock entirely when adquired via foo.bar("test-match").getBaz() and constructed via new Baz(callback):

//setup using()
using(foo, 'bar').like( foo.bar("test-match") ).follow(function(obj){

  return using(obj, 'getBaz');

}).follow(function(Baz){

  //Notice that because we intercept Baz itself in .follow() we can modify its entire behaviour.

  //This wouldn't be possible if Baz reference hadn't been piped through using.follow(),
  //in that case we would only be able to intercept methods of Baz, but not its instances

  using(Baz).like( new Baz(using.aCallback) ).stub(function(originalFn, args, context){
    var callback = args[0];
    callback("YEAH!");
  });

});

//actual test case
var obj = foo.bar("test-match"); //anything else won't apply
var Baz = new obj.getBaz();

new Baz(function(str){
  console.log(str);
}) //prints "YEAH!"

Node.js require()

Intercepting require() in Node.js:

Consider the example object at the top of this documentation is encapsulated in node module example.js

//example.js

// foo code
// ...


module.exports = foo;

Let's intercept and modify it

//
using.require('./example').first().follow(function(foo){

  using(foo, 'bar')
  .follow(function(){

    console.log("HI!")

  });

})

//running the test
var foo = require('./example')

foo.bar(); //prints "HI!"

Notes:

  • The using.require() interface only has a forceful .first() API available (not a direct .follow()) - this happens because a require() in node always returns the same instance and thus .first() avoids triggering a .follow() every time a require() is called per file and avoids overriding behaviour on that instance over and over
  • using will automatically pin the rule (eg. using.require(path)) to the target file given by the relative path to the current module path, meaning it will override the target module even if you require it from a different module later located on any other relative path (subfolders, etc)
  • using.require(module) also works for any module (eg. 'http' - not necessarily a path) and the match in this case is pinned down to all calls to the given module string exactly (independent of module location or dependecy resolution)

Cleaning rules

Examples on removing all or part of set rules :

//clean all rules for a given method
using(obj, 'prop').clean();

//clean all rules for classes / functions
using(obj).clean();

//clean all rules for module
using.require('module').clean();

//clean everything - all rules for all objects and methods, modules, classes, etc
using.clean();

//Note: .clean() only cleans rules that have been set via the given using instance (see first item on documentation)

matchers

parameter matchers

The simplest way to exactly match a parameter is by specifying it directly.

using(foo, 'bar').like( foo.bar(5) );

//
foo.bar(5); //matches
foo.bar("5"); //does not match

Or, you can use any callback as a matcher (returning true matches)

function divisibleBy3(param){
  return typeof(param)==='number' && (param % 3) === 0;
}

using(foo, 'bar').like( foo.bar(divisibleBy3) );

//
foo.bar(6); //matches
foo.bar(7); //does not match

Context matchers

Parameter matching even works on context

using(foo, 'bar').like( foo.bar.call(using.anObjectLike({'a':'a'}), 5) );

//
foo.bar.apply({'a':'a'}, [5]); //matches
foo.bar(5); //does not match

using-stubs provides you a few common matchers for ease of use

using.aString            //matches any string
using.aStringLike(regex) //matches the regular expression
using.anInt              //matches any integer
using.aNumber            //matches any number
using.anObject           //matches any object (not null)
using.aFunction          //matches any function
using.typeOf(type)       //tests typeOf(parameter)===type
using.instanceOf(Class)  //tests parameter instanceOf Class
using.something          //matches parameter!==undefined
using.anything           //matches any param as long as it is set in the argument list (even undefined)

using.atLeast(x)     //a number > x
using.atMost(x)      //a number < x
using.between(x, y)  //a number > x && < y
using.oneOf(a1, [[a2], ...[aN]])     //matches one of the given parameters
using.otherThan(a1, [[a2], ...[aN]]) //matches if none of the given parameters match

using.anObjectLike(obj, [boolean strict])  //deep compare to given object
                         //strict - defaults to non-strict (==) comparison (false)

using.everything         //special matcher - all arguments from this point onward
                         //will be matched, even if not set in the argument list.
                         //Eg. foo("a", using.everything) will match foo("a"), foo("a", "one")
                         //or even foo("a", 1, 2, 3, 4, 5, 6);


Putting it all together

An example using the most relevant API methods all at once:


var scope = {};

//using most API methods in one chain
using.require('./example').first().follow(function(foo){

  using(foo, 'bar')
  .like( foo.bar.call(scope, using.aString) ) //only when called like this
  .stub(function(fn, args, context){

    //eg. replace a calling argument but still return the original function result
    return fn.call(context, "my-override-string")

  })
  .the(2) //stalker-pattern API: only follow the 2nd match
  .follow(function(obj){

    console.log("the(2) foo.bar()");
    return using(obj, 'Baz'); //chaining with .follow()

  })
  .first() //stalker-pattern API: only follow the first instance of obj.Baz
  .follow(function(bazInstance){

    console.log("first() instance of obj.Baz");
    return using(bazInstance, 'test'); //chaining with .follow()
  })
  .from(2).to(5) //stalker-pattern API: only follow 2nd to 5th call to bazInstance.test()
  .follow(function(res){
    console.log("+1");
  })

})

//running the test
foo.bar("-"); //does nothing (does not match on context)

var a = foo.bar.call(scope, "a"); //does nothing (1st match)
var b = foo.bar.call(scope, "b"); //prints "the(2) foo.bar()"

var instance = new b.Baz(); //prints "first() instance of obj.Baz"

instance.test(); //does nothing

instance.test(); //prints "+1"
instance.test(); //prints "+1"
instance.test(); //prints "+1"
instance.test(); //prints "+1"

instance.test(); //does nothing

Note: The interfaces .first(), .the(), .from(), .to(), and .follow() are inherited from the stalker-pattern API Take a look at stalker-pattern reference in order to further understand the pattern and its chaining API