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.
Related
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.
I'm looking to hook to console.log in electron.
I tried following the steps as mentioned here: https://stackoverflow.com/a/9624028/2091948
While the above works well in node.js, this same approach doesn't work in electron (which in-turn is node.js based)
How can I achieve this in electron?
I need to capture all calls to console.log, and console.error, so that I can use the module electron-log(https://www.npmjs.com/package/electron-log) to hook to all console.log, and console.error calls.
I think this could be done just via overriding console.log or console.error like this:
var log = require("electron-log");
console.log = function (message) {
log.info(message);
}
console.error = function (message) {
log.error(message);
}
Alternatively, if you like to preserve the old functions and/or store all messages in an array for later use, you could use the following functions:
var log = require("electron-log"), msgInfo = [], msgErr = [];
// Preserve the old, built-in functions
console.log_old = console.log;
console.error_old = console.error;
console.log = function (message) {
msgInfo.push(message);
log.info(message);
}
console.error = function (message) {
msgErr.push(message);
log.error(message);
}
You can use these functions in a browser window, for the main process, the code you already mentioned should work with some adaptions:
// https://stackoverflow.com/a/9624028, written by "kevin"
var logs = [],
hook_stream = function(_stream, fn) {
// Reference default write method
var old_write = _stream.write;
// _stream now write with our shiny function
_stream.write = fn;
return function() {
// reset to the default write method
_stream.write = old_write;
};
},
// hook up standard output
unhook_stdout = hook_stream(process.stdout, function(string, encoding, fd) {
logs.push(string);
}),
// require electron-log
log = require("electron-log");
// goes to our custom write method
console.log('foo');
console.log('bar');
unhook_stdout();
console.log('Not hooked anymore.');
// Now do what you want with logs stored by the hook
logs.forEach(function(_log) {
// Either log the contents of the message array...
//console.log('logged: ' + _log);
// ...or use electron-log to actually log them.
log.info(_log);
});
This code will work in the main process, because there NodeJS creates the stream to stdout. In the renderer process you will have to use the functions I mentioned above, because Electron doesn't create any stream but rather lets Chromium log all messages to it's Developer Console.
You can hook up to the console-message event of the WebContent object (available from the webContent property on BrowserView and BrowserWindow).
Note that it only provides it as a string so if you are loggign an object it will be received as [object Object]
For more info see the docs
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.
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() ;
}
This question relates to the Mocha testing framework for NodeJS.
The default behaviour seems to be to start all the tests, then process the async callbacks as they come in.
When running async tests, I would like to run each test after the async part of the one before has been called.
How can I do this?
The point is not so much that "structured code runs in the order you've structured it" (amaze!) - but rather as #chrisdew suggests, the return orders for async tests cannot be guaranteed. To restate the problem - tests that are further down the (synchronous execution) chain cannot guarantee that required conditions, set by async tests, will be ready they by the time they run.
So if you are requiring certain conditions to be set in the first tests (like a login token or similar), you have to use hooks like before() that test those conditions are set before proceeding.
Wrap the dependent tests in a block and run an async before hook on them (notice the 'done' in the before block):
var someCondition = false
// ... your Async tests setting conditions go up here...
describe('is dependent on someCondition', function(){
// Polls `someCondition` every 1s
var check = function(done) {
if (someCondition) done();
else setTimeout( function(){ check(done) }, 1000 );
}
before(function( done ){
check( done );
});
it('should get here ONLY once someCondition is true', function(){
// Only gets here once `someCondition` is satisfied
});
})
use mocha-steps
it keeps tests sequential regardless if they are async or not (i.e. your done functions still work exactly as they did). It's a direct replacement for it and instead you use step
I'm surprised by what you wrote as I use. I use mocha with bdd style tests (describe/it), and just added some console.logs to my tests to see if your claims hold with my case, but seemingly they don't.
Here is the code fragment that I've used to see the order of "end1" and "start1". They were properly ordered.
describe('Characters start a work', function(){
before(function(){
sinon.stub(statusapp, 'create_message');
});
after(function(){
statusapp.create_message.restore();
});
it('creates the events and sends out a message', function(done){
draftwork.start_job(function(err, work){
statusapp.create_message.callCount.should.equal(1);
draftwork.get('events').length.should.equal(
statusapp.module('jobs').Jobs.get(draftwork.get('job_id')).get('nbr_events')
);
console.log('end1');
done();
});
});
it('triggers work:start event', function(done){
console.log('start2');
statusapp.app.bind('work:start', function(work){
work.id.should.equal(draftwork.id);
statusapp.app.off('work:start');
done();
});
Of course, this could have happened by accident too, but I have plenty of tests, and if they would run in parallel, I would definitely have race conditions, that I don't have.
Please, refer to this issue too from the mocha issue tracker. According to it, tests are run synchronously.
I wanted to solve this same issue with our application, but the accepted answer didn't work well for us. Especially in the someCondition would never be true.
We use promises in our application and these made it very easy to structure the tests accordingly. The key however is still to delay execution through the before hook:
var assert = require( "assert" );
describe( "Application", function() {
var application = require( __dirname + "/../app.js" );
var bootPromise = application.boot();
describe( "#boot()", function() {
it( "should start without errors", function() {
return bootPromise;
} );
} );
describe( "#shutdown()", function() {
before( function() {
return bootPromise;
} );
it( "should be able to shut down cleanly", function() {
return application.shutdown();
} );
} );
} );