├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## node-monkeypatch [![Build Status](https://travis-ci.org/robertklep/node-monkeypatch.svg?branch=master)](https://travis-ci.org/robertklep/node-monkeypatch) 2 | 3 | 4 | Slightly easier monkeypatching. 5 | 6 | ### Installation 7 | 8 | ``` 9 | npm install monkeypatch 10 | ``` 11 | 12 | ### Usage 13 | 14 | ```javascript 15 | monkeypatch(target : Object, method : String, handler : Function) : Function 16 | ``` 17 | 18 | Monkeypatching a method/function on a target object replaces the method with a newly supplied handler function which will get called instead of the original. 19 | 20 | The original method will be returned by `monkeypatch()`, and will also be passed as the first argument to the new handler function. See examples. 21 | 22 | ### Examples 23 | 24 | ##### Patching a function 25 | 26 | ```javascript 27 | var monkeypatch = require('monkeypatch'); 28 | 29 | // Monkeypatch Date.now() 30 | monkeypatch(Date, 'now', function(original) { 31 | // Round to 15-minute interval. 32 | var ts = original(); 33 | return ts - (ts % 900000); 34 | }); 35 | 36 | var timestamp = Date.now(); // returns a rounded timestamp 37 | ... 38 | ``` 39 | 40 | ##### Patching an instance method 41 | 42 | ```javascript 43 | var monkeypatch = require('monkeypatch'); 44 | 45 | // Monkeypatch Date#getTime() 46 | monkeypatch(Date.prototype, 'getTime', function(original) { 47 | // Round to 15-minute interval. 48 | var ts = original(); 49 | return ts - (ts % 900000); 50 | }); 51 | 52 | var date = new Date(); 53 | var timestamp = date.getTime(); // returns a rounded timestamp 54 | ... 55 | ``` 56 | 57 | ##### Argument handling 58 | 59 | ```javascript 60 | var monkeypatch = require('monkeypatch'); 61 | 62 | // Monkeypatch Date#setTime() 63 | monkeypatch(Date.prototype, 'setTime', function(original, ts) { 64 | // Round to 15-minute interval. 65 | ts = ts - (ts % 900000); 66 | // Call the original. 67 | return original(ts); 68 | }); 69 | 70 | var date = new Date(); 71 | date.setTime(date.getTime()); // set to a rounded timestamp 72 | ... 73 | ``` 74 | 75 | ##### Unpatching 76 | 77 | ```javascript 78 | var monkeypatch = require('monkeypatch'); 79 | 80 | // Monkeypatch Date.now() 81 | monkeypatch(Date, 'now', function() { return 143942400000; }); 82 | 83 | console.log(Date.now()); // logs 143942400000 84 | 85 | Date.now.unpatch(); 86 | 87 | console.log(Date.now()); // logs current time 88 | ``` 89 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(obj, method, handler, context) { 2 | var original = obj[method]; 3 | 4 | // Unpatch first if already patched. 5 | if (original.unpatch) { 6 | original = original.unpatch(); 7 | } 8 | 9 | // Patch the function. 10 | obj[method] = function() { 11 | var ctx = context || this; 12 | var args = [].slice.call(arguments); 13 | args.unshift(original.bind(ctx)); 14 | return handler.apply(ctx, args); 15 | }; 16 | 17 | // Provide "unpatch" function. 18 | obj[method].unpatch = function() { 19 | obj[method] = original; 20 | return original; 21 | }; 22 | 23 | // Return the original. 24 | return original; 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monkeypatch", 3 | "version": "1.0.0", 4 | "description": "Slightly easier monkeypatching", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha -R spec --bail test.js" 8 | }, 9 | "author": "Robert Klep ", 10 | "repository" : "robertklep/node-monkeypatch", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "chai": "^2.3.0", 14 | "mocha": "^2.2.5", 15 | "mocha-sinon": "^1.1.4", 16 | "sinon": "^1.14.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var mochasinon = require('mocha-sinon'); 2 | var expect = require('chai').expect; 3 | var monkeypatch = require('./index.js'); 4 | 5 | describe('monkeypatch()', function() { 6 | var stub; 7 | 8 | beforeEach(function() { 9 | stub = this.sinon.stub(Date, 'now').returns(11111); 10 | monkeypatch(Date, 'now', function(original) { 11 | original(); // calls stub 12 | return 12345; 13 | }); 14 | }); 15 | 16 | afterEach(function() { 17 | Date.now.unpatch && Date.now.unpatch(); 18 | }); 19 | 20 | describe('patching', function() { 21 | var ret; 22 | 23 | beforeEach(function() { 24 | ret = Date.now(); 25 | }); 26 | 27 | it('should call the original method only once', function() { 28 | expect(stub.calledOnce).to.be.true; 29 | }); 30 | 31 | it('should return the value from the newly provided method', function() { 32 | expect(ret).to.equal(12345); 33 | }); 34 | 35 | }); 36 | 37 | describe('repatching', function() { 38 | var ret; 39 | 40 | beforeEach(function() { 41 | monkeypatch(Date, 'now', function(original) { 42 | original(); 43 | return 13579; 44 | }); 45 | ret = Date.now(); 46 | }); 47 | 48 | it('should call the original method only once', function() { 49 | expect(stub.calledOnce).to.be.true; 50 | }); 51 | 52 | it('should return the value from the repatched method', function() { 53 | expect(ret).to.equal(13579); 54 | }); 55 | }); 56 | 57 | describe('unpatching', function() { 58 | it('should be possible to unpatch methods', function() { 59 | Date.now.unpatch(); 60 | expect(Date.now()).to.equal(11111); 61 | }); 62 | 63 | it('should be possible to repatch unpatched methods', function() { 64 | Date.now.unpatch(); 65 | monkeypatch(Date, 'now', function(original) { return 13579; }); 66 | expect(Date.now()).to.equal(13579); 67 | }); 68 | }); 69 | 70 | describe('prototype patching', function() { 71 | var instance; 72 | 73 | beforeEach(function() { 74 | monkeypatch(Test.prototype, 'incr', function(original, v) { 75 | return v + 2; 76 | }); 77 | instance = new Test(); 78 | }); 79 | 80 | afterEach(function() { 81 | Test.prototype.incr.unpatch && Test.prototype.incr.unpatch(); 82 | }); 83 | 84 | it('should be possible to patch prototype methods', function() { 85 | expect(instance.incr(3)).to.equal(5); 86 | }); 87 | 88 | it('should be possible to unpatch prototype methods', function() { 89 | Test.prototype.incr.unpatch(); 90 | expect(instance.incr(3)).to.equal(4); 91 | }); 92 | }); 93 | 94 | describe('calling context', function() { 95 | var instance; 96 | 97 | beforeEach(function() { 98 | monkeypatch(Test.prototype, 'getName', function(original) { 99 | var value = original(); 100 | return value + ' ABC'; 101 | }); 102 | instance = new Test(); 103 | }); 104 | 105 | afterEach(function() { 106 | Test.prototype.getName.unpatch && Test.prototype.getName.unpatch(); 107 | }); 108 | 109 | it('should use the proper calling context', function() { 110 | expect(instance.getName()).to.equal('this is test ABC'); 111 | }); 112 | 113 | it('should use the proper calling context after unpatching', function() { 114 | Test.prototype.getName.unpatch(); 115 | expect(instance.getName()).to.equal('this is test'); 116 | }); 117 | 118 | }); 119 | 120 | describe('async', function() { 121 | var fs = require('fs'); 122 | 123 | beforeEach(function() { 124 | monkeypatch(fs, 'stat', function(original, path, callback) { 125 | return original(path, function(err, stats) { 126 | if (stats) stats.patched = true; 127 | return callback(err, stats); 128 | }); 129 | }); 130 | }); 131 | 132 | afterEach(function() { 133 | fs.stat.unpatch && fs.stat.unpatch(); 134 | }); 135 | 136 | it('should be possible to patch async functions', function(done) { 137 | fs.stat(__filename, function(err, stats) { 138 | if (err) throw err; 139 | expect(stats.patched).to.be.true; 140 | return done(); 141 | }); 142 | }); 143 | 144 | it('should be possible to unpatch async functions', function(done) { 145 | fs.stat.unpatch(); 146 | fs.stat(__filename, function(err, stats) { 147 | if (err) throw err; 148 | expect(stats.patched).to.not.exist; 149 | return done(); 150 | }); 151 | }); 152 | 153 | }); 154 | 155 | }); 156 | 157 | // Test class. 158 | function Test() { this.name = 'this is test'; } 159 | Test.prototype.incr = function(v) { return v + 1; }; 160 | Test.prototype.getName = function() { return this.name; }; 161 | --------------------------------------------------------------------------------