Fake calling of a function with specific arguments in Sinon - javascript

I have been on this for quite long and maybe I'm just missing something but my research did not yield any results that would help me.
So my question is:
If I have some code like this:
shell.on('message', function (message) {
// do something
});
And I want to test it as if it had been called with a certain message (or even an error), could I somehow do that with Sinon? ( just putting the do something in an external function will only work to some degree so I do hope for an answer that at least has a way to fake-call shell.on to test if the inner function gets invoked).
The "shell" is an instance of the shell of the npm Package "Python-Shell"
Maybe it's not possible at all or maybe I'm just blind but any help is much appreciated!

The python-shell instance is instance of an EventEmitter. So you can cause the on handler to fire by just emitting the message:
var PythonShell = require('python-shell');
var pyshell = new PythonShell('my_script.py');
pyshell.on('message', function (message) {
console.log("recieved", message);
});
pyshell.emit('message', "fake message?")
// writes: 'recieved fake message?'
You can also stub the instance with Sinon and call yields to call the callback:
const sinon = require('sinon')
var PythonShell = require('python-shell');
var pyshell = new PythonShell('my_script.py');
var stub = sinon.stub(pyshell, "on");
stub.yields("test message")
// writes received test message to console
pyshell.on('message', function (message) {
console.log("received", message);
});
This might be more useful if you don't want to prevent the default behavior when running a test.

Related

How to stub a method that is called from an outer scope to the function under test?

I have a Redis client that is created thus using the node_redis library (https://github.com/NodeRedis/node_redis):
var client = require('redis').createClient(6379, 'localhost');
I have a method I want to test whose purpose is to set and publish a value to Redis, so I want to test to ensure the set and publish methods are called or not called according to my expectations. The tricky thing is I want this test to work without needing to fire up an instance of a Redis server, so I can't just create the client because it will throw errors if it cannot detect Redis. Therefore, I need to stub the createClient() method.
Example method:
// require('redis').createClient(port, ip) is called once and the 'client' object is used globally in my module.
module.exports.updateRedis = function (key, oldVal, newVal) {
if (oldVal != newVal) {
client.set(key, newVal);
client.publish(key + "/notify", newVal);
}
};
I've tried several ways of testing whether set and publish are called with the expected key and value, but have been unsuccessful. If I try to spy on the methods, I can tell my methods are getting called by running the debugger, but calledOnce is not getting flagged as true for me. If I stub the createClient method to return a fake client, such as:
{
set: function () { return 'OK'; },
publish: function () { return 1; }
}
The method under test doesn't appear to be using the fake client.
Right now, my test looks like this:
var key, newVal, oldVal, client, redis;
before(function () {
key = 'key';
newVal = 'value';
oldVal = 'different-value';
client = {
set: function () { return 'OK'; },
publish: function () { return 1; }
}
redis = require('redis');
sinon.stub(redis, 'createClient').returns(client);
sinon.spy(client, 'set');
sinon.spy(client, 'publish');
});
after(function () {
redis.createClient.restore();
});
it('sets and publishes the new value in Redis', function (done) {
myModule.updateRedis(key, oldVal, newVal);
expect(client.set.calledOnce).to.equal(true);
expect(client.publish.calledOnce).to.equal(true);
done();
});
The above code gives me an Assertion error (I'm using Chai)
AssertionError: expected false to equal true
I also get this error in the console logs, which indicates the client isn't getting stubbed out when the method actually runs.
Error connecting to redis [Error: Ready check failed: Redis connection gone from end event.]
UPDATE
I've since tried stubbing out the createClient method (using the before function so that it runs before my tests) in the outer-most describe block of my test suite with the same result - it appears it doesn't return the fake client when the test actually runs my function.
I've also tried putting my spies in the before of the top-level describe to no avail.
I noticed that when I kill my Redis server, I get connection error messages from Redis, even though this is the only test (at the moment) that touches any code that uses the Redis client. I am aware that this is because I create the client when this NodeJS server starts and Mocha will create an instance of the server app when it executes the tests. I'm supposing right now that the reason this isn't getting stubbed properly is because it's more than just a require, but the createClient() function is being called at app startup, not when I call my function which is under test. I feel there still ought to be a way to stub this dependency, even though it's global and the function being stubbed gets called before my test function.
Other potentially helpful information: I'm using the Gulp task runner - but I don't see how this should affect how the tests run.
I ended up using fakeredis(https://github.com/hdachev/fakeredis) to stub out the Redis client BEFORE creating the app in my test suite like so:
var redis = require('fakeredis'),
konfig = require('konfig'),
redisClient = redis.createClient(konfig.redis.port, konfig.redis.host);
sinon.stub(require('redis'), 'createClient').returns(redisClient);
var app = require('../../app.js'),
//... and so on
And then I was able to use sinon.spy in the normal way:
describe('some case I want to test' function () {
before(function () {
//...
sinon.spy(redisClient, 'set');
});
after(function () {
redisClient.set.restore();
});
it('should behave some way', function () {
expect(redisClient.set.called).to.equal(true);
});
});
It's also possible to mock and stub things on the client, which I found better than using the redisErrorClient they provide for testing Redis error handling in the callbacks.
It's quite apparent that I had to resort to a mocking library for Redis to do this because Sinon couldn't stub out the redisClient() method as long as it was being called in an outer scope to the function under test. It makes sense, but it's an annoying restriction.

How stub a global dependency's new instance method in nodejs with sinon.js

Sorry for the confusing title, I have no idea how to better describe it. Let's see the code:
var client = require('some-external-lib').createClient('config string');
//constructor
function MyClass(){
}
MyClass.prototype.doSomething = function(a,b){
client.doWork(a+b);
}
MyClass.prototype.doSomethingElse = function(c,d){
client.doWork(c*d);
}
module.exports = new MyClass();
Test:
var sinon = require('sinon');
var MyClass = requre('./myclass');
var client = require('some-external-lib').createClient('config string');
describe('doSomething method', function() {
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork');
MyClass.doSomething();
assert(stub.calledOnce); //not working! returns false
})
})
I could get it working if .createClient('xxx') is called inside each method instead, where I stub client with:
var client = require('some-external-lib');
sinon.stub(client, 'createClient').returns({doWork:function(){})
But it feels wrong to init the client everytime the method each being called.
Is there a better way to unit test code above?
NEW: I have created a minimal working demo to demonstrate what I mean: https://github.com/markni/Stackoverflow30825202 (Simply npm install && npm test, watch the test fail.) This question seeks a solution make the test pass without changing main code.
The problem arises at the place of test definition. The fact is that in Node.js it is rather difficult to do a dependency injection. While researching it in regard of your answer I came across an interesting article where DI is implemented via a custom loadmodule function. It is a rather sophisticated solution, but maybe eventually you will come to it so I think it is worth mentioning. Besides DI it gives a benefit of access to private variables and functions of the tested module.
To solve the direct problem described in your question you can stub the client creation method of the some-external-lib module.
var sinon = require('sinon');
//instantiate some-external-lib
var client = require('some-external-lib');
//stub the function of the client to create a mocked client
sinon.stub(client, 'createClient').returns({doWork:function(){})
//due to singleton nature of modules `require('some-external-lib')` inside
//myClass module will get the same client that you have already stubbed
var MyClass = require('./myclass');//inside this your stubbed version of createClient
//will be called.
//It will return a mock instead of a real client
However, if your test gets more complicated and the mocked client gets a state you will have to manually take care of resetting the state between different unit tests. Your tests should be independent of the order they are launched in. That is the most important reason to reset everything in beforeEach section
You can use beforeEach() and afterEach() hooks to stub global dependency.
var sinon = require('sinon');
var MyClass = requre('./myclass');
var client = require('some-external-lib').createClient('config string');
describe('doSomething method', function() {
beforeEach(function () {
// Init global scope here
sandbox = sinon.sandbox.create();
});
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork').yield();
MyClass.doSomething();
assert(stub.calledOnce); //not working! returns false
})
afterEach(function () {
// Clean up global scope here
sandbox.restore();
});
})
Part of the problem is here: var stub = sinon.stub(client,'doWork').yield();
yield doesn't return a stub. In addition, yield expects the stub to already have been called with a callback argument.
Otherwise, I think you're 95% of the way there. Instead of re-initializing for every test, you could simply remove the stub:
describe('doSomething method', function() {
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork');
MyClass.doSomething();
assert(stub.calledOnce);
stub.restore();
})
})
BTW, another poster suggested using Sinon sandboxes, which is a convenient way to automatically remove stubs.

Issue with callbacks in Cucumber.js scenario with ZombieJS

I've got some experience with BDD tools like Cucumber and Lettuce. I'm currently building a Phonegap app, and I'd like to start using Cucumber.js to create acceptance tests for it. Unfortunately I'm having a bit of an issue.
Here is the basic feature file I've thrown together:
Feature: Authentication
As a user
I want to be able to log in and out
Scenario: Logging in
Given I am not logged in
And I am on the page "login"
When I fill in the "username" field with "student"
And I fill in the "password" field with "password"
And I click the "LOG IN" button
Then I should see the text "STUDENT"
Here is my world.js:
var zombie = require('zombie');
var World = function World(callback) {
"use strict";
this.browser = new zombie(); // this.browser will be available in step definitions
this.visit = function (url, callback) {
this.browser.visit(url, callback);
};
callback(); // tell Cucumber we're finished and to use 'this' as the world instance
};
exports.World = World;
Here are my step definitions:
var wrapper = function () {
"use strict";
this.World = require("../support/world.js").World; // overwrite default World constructor
this.Given(/^I am not logged in$/, function (callback) {
// Clear local storage
this.browser.localStorage("localhost:9001").clear();
callback();
});
this.Given(/^I am on the page "([^"]*)"$/, function (page, callback) {
// Visit page
this.browser.visit('http://localhost:9001/app/index.html#' + page, callback);
});
};
module.exports = wrapper;
I've set up a Grunt task that first runs the connect server on port 9001, then runs the Cucumber scenarios. The documentation for Cucumber.js implies this should work, but it fails on the second step.
Here is the error message I get:
Running "connect:cucumber" (connect) task
Started connect web server on http://localhost:9001
Running "cucumberjs:src" (cucumberjs) task
.Cannot call method 'add' of undefined TypeError: Cannot call method 'add' of undefined
at <anonymous>:10:711
at <anonymous>:10:874
at <anonymous>:10:1224
at Contextify.sandbox.run (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/node_modules/contextify/lib/contextify.js:12:24)
at DOMWindow.window._evaluate (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/window.js:188:25)
at Object.HTML.languageProcessors.javascript (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:23:21)
at define.proto._eval (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:1480:47)
at loaded (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:74:23)
at /Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:76:20
at Object.item.check (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:345:11)
FUUUU
(::) failed steps (::)
TypeError: Cannot call method 'add' of undefined
at <anonymous>:10:711
at <anonymous>:10:874
at <anonymous>:10:1224
at Contextify.sandbox.run (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/node_modules/contextify/lib/contextify.js:12:24)
at DOMWindow.window._evaluate (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/window.js:188:25)
at Object.HTML.languageProcessors.javascript (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:23:21)
at define.proto._eval (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:1480:47)
at loaded (/Users/matthewdaly/Projects/myapp/node_modules/zombie/lib/zombie/scripts.js:74:23)
at /Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:76:20
at Object.item.check (/Users/matthewdaly/Projects/myapp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:345:11)
If I insert callback(); after the body of the second step, it passes. I'm not sure what's going on. Why is this scenario failing? The app itself works as expected. It seems like the callback for the second step is never firing.
the test passes if you add the callback to the second step, because than visitPage is just skipped.
my visit function looks like this:
this.visit = function(url, callback) {
that.browser.visit(url, function(error) {
if (error) {
callback.fail(error);
} else {
callback.call(that, that.browser);
}
});
});
but I think the real problem is on your page, because sandbox.run is the point where zombie starts to execute custom (js)-code from the page. So it's an anonymous callback in your (minified) script in column 1224?
Maybe you have to track it down with console.log... (something with localStorage?, allthough zombie supports it), grep for 'add" in your custom code
Why use callbacks at all? They obfuscate your code. Whereas, the equivalent is to use the async/await pairs, which will mimic, so to speak, java coding and proper instructions starting and ending :
var R = await visit () ;
await do_this_when_visit_is_done () ;
await do_that_when_do_this_is_done() ;
in cucumber :
this.Given(/^I am on the page "(.*)"$/, async function (page)
{
await this.page_is_loaded() ;
}

cannot denodeify methods in node-ftp module

I am new to both node.js and promise style function call. By looking at an denodeify example at http://runnable.com/Ulatc0QnzUgUAAAK/adapting-node-js-with-q-for-promises, I am trying to denodeify the methods of the node.js node-ftp module as following:
var ftp = require('ftp');
var q = require('q');
var ftpClient = new ftp();
ftpClient.on('ready', function() {
var ftpList = q.denodeify(ftpClient.list);
ftpList().then(function(list) {
console.log(list);
}.then(null, function(err) {
console.log(err);
}).done(function() {
ftpClient.end();
});
});
ftpClient.connect();
However, when running that code with node, it shows the error "list error: TypeError: Object # has no method '_pasv'"
I am not sure what's wrong with that piece of code. Does anyone know what's wrong with that? Can you point me some way to debug/troubleshoot the cause of that error message?
Thanks.
When you pass
ftpClient.list
to Q.denodefiy, you are getting the function object, list from the ftpClient object. It will be just a function and the relationship with the parent is lost. This is important because, the bound function list might be dependent on the ftpClient object. So, you must make sure that link is not broken.
Quoting from the Q.denodeify docs,
Note that if you have a method that uses the Node.js callback pattern,
as opposed to just a function, you will need to bind its this value
before passing it to denodeify, like so:
var Kitty = mongoose.model("Kitty");
var findKitties = Q.denodeify(Kitty.find.bind(Kitty));
The better strategy for methods would be to use Q.nbind, as shown below.
So, you can fix your code in two ways,
Using Q.denodeify and Function.prototype.bind, like this
var ftpList = q.denodeify(ftpClient.list.bind(ftpClient));
Using Q.nbind, like this
var ftpList = q.nbind(ftpClient.list, ftpClient);
you need to use q.nbind
q.nbind(ftpClient.list, ftpClient);

Detecting console.log() calls

I'm trying to write a test case for a debugging method that writes messages to the JavaScript console using console.log(). The test has to check that the message has been successfully written to the console. I'm using jQuery.
Is there a way to attach a hook to console.log() or otherwise check that a message has been written to the console, or any other suggestions on how to write the test case?
console.log doesn't keep a record of messages that are logged, or emit any events that you could listen for. It's not possible for your tests to directly verify its output from JavaScript. Instead, your test code will need to replace console.log with a mock implementation that does keep track of log messages for later verification.
Mocking is a common feature supported by most JavaScript test frameworks. For example, the Jest test framework provides a jest.spyOn function which replaces a given method with a mock implementation that records the arguments for each call in a .mock property before passing them on to the original implementation. After each test you may want to call jest.clearAllMocks() to reset the recorded argument lists for the next test, or use the equivalent clearMocks: true config option.
function saySomething() {
console.log("Hello World");
}
jest.spyOn(console, 'log');
test("saySomething says hello", () => {
expect(console.log.mock.calls.length).toBe(0);
saySomething();
expect(console.log.mock.calls.length).toBe(1);
expect(console.log.mock.calls[0][0]).toBe("Hello World");
});
afterEach(() => {
jest.clearAllMocks();
});
If you're not using a test framework (you probably should), you can create a simple mock yourself.
function saySomething() {
console.log("Hello World");
}
function testSomething() {
// Replace console.log with stub implementation.
const originalLog = console.log;
const calls = [];
console.log = (...args) => {
calls.push(args);
originalLog(...args);
};
try {
console.assert(calls.length == 0);
saySomething();
console.assert(calls.length == 1);
console.assert(calls[0][0] == "Hello World");
} catch (error) {
console.error(error);
} finally {
// Restore original implementation after testing.
console.log = originalLog;
}
}
So not bad solutions, but if you're looking for a high powered logger try Paul Irish's log()
If that's too high powered, you can get by with something like this.
var console = window.console,
_log = console ? console.log : function(){};
_log.history = [];
console.log = function( ){
_log.history.push.apply( _log.history, arguments );
_log.apply( console, arguments );
}
Usage
console.log('I','have','an','important','message');
//Use native one instead
_log.call( console, _log.history );
http://jsfiddle.net/BeXdM/
If you're using Jasmine, it's dead simple:
it('is my test', function () {
spyOn(console, 'log');
// do your stuff that should log something
expect(console.log).toHaveBeenCalledWith('something');
});
Head to Jasmine docs for more info.
Just attach your own function to console.log.
On your page, after everything loads,
Before starting tests -
var originalLog = console.log;
console.log = function(msg){
alert('my .log hook received message - '+msg);
//add your logic here
}
After running tests, if necessary -
console.log = originalLog
Probably the easiest way out is to use the NPM package std-mocks.
From their documentation:
var stdMocks = require('std-mocks');
stdMocks.use();
process.stdout.write('ok');
console.log('log test\n');
stdMocks.restore();
var output = stdMocks.flush();
console.log(output.stdout); // ['ok', 'log test\n']
Note: make sure you stdMocks.restore() before your assertions so your test runner is still able to log information about failed assertions.

Categories

Resources