Issue with callbacks in Cucumber.js scenario with ZombieJS - javascript

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() ;
}

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.

Set a Javascript function to be all the code in another .js file

I have a file doSomething.js that contains some code that needs to be run both from the command line (eg: node doSomething.js) and also from a nodejs-based queue worker
queueWorker.js
var worker = client.worker(['example']);
worker.register({
doSomething: function (params, callback) {
// run the code contained in doSomething.js
}
});
worker.start();
doSomething.js
console.log('Hello world')
Question: Is there a way to let the main code remain in doSomething.js and just include a reference to doSomething.js in queueWorker.js? I dont want to have 2 copies of the same code in 2 different files.
So if I understand you correctly you've some code in a project that you want to be used by a command-line node script and by a worker. You don't mention how you deploy this code, so I'm going to assume that the stuff running the worker is the same codebase as the command line.
In which case I would assume that one solution is to make your doSomething.js use the standard NodeJS module system, i.e. something like
doSomething.js
module.exports = function() {
console.log('Hello, world');
}
queueWorker.js
Your worker would become something like:
var worker = client.worker(['example']);
var doSomething = require('path/to/doSomething');
worker.register({
doSomething: function (params, callback) {
doSomething();
}
});
worker.start();
doSomethingCli.js
Your command line script would be a totally separate script to the above (assuming that's okay), looking something similar to the worker:
var doSomething = require('path/to/doSomething');
doSomething();
While you stated you wanted to have doSomething.js callable from the command line that isn't a great solution - you'll find yourself starting to put cli stuff into doSomething.js which will break your queue worker. Keep the common code separate.
I assume you are using node for both cases.
queueWorker.js
var doSomething = require('./doSomething');
var worker = client.worker(['example']);
worker.register({
doSomething: function (params, callback) {
doSomething(params, callback);
}
});
worker.start();
doSomething.js
module.exports = functionv(params, callback) {
console.log('Hello world');
}
Javascript is a dynamic language and it's always possible to eval a string containing code. For example you can do this
// file1.js -------------------------
function foo() {
console.log("Hello, world.");
}
// file2.js -------------------------
eval(require('fs').readFileSync("file1.js", "utf-8"));
foo();
Of course node.js contains a much better approach if the code is indeed static and you just want to share it between different projects (the idea of modules).

before() not executing before subsequent describes()?

Given the following code :
var api = {};
var models = {};
describe(vars.project.name, function() {
before(function(done) {
// Loading models
models_module.getDbInstance('0.1', function(res) {
db = res;
var config = {
server: server,
db: db,
v: '0.1'
};
// Importing all the tests
api.base = require('./functions/api.base.js')(config);
models.test_table = require('./functions/models.test_table.js')(config);
done();
});
});
// Tests general status
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', api.base.status.without_route);
});
});
This loads the database with my models for version 0.1 and then require my tests definitions, passing it the database and some other config infos. This is in theory.
Instead, I get an error saying Cannot read property 'status' of undefined. This means that it tries to execute my tests, or at least initialize it, before completing the before function.
I also tried with async.series (loading the models, then loading the tests) but it doesn't do anything, only displays 0 passing (0ms).
What's wrong with this?
I saw the tree and missed the forest...
This cannot work:
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', api.base.status.without_route);
});
The problem is that you are trying to evaluate api.base while Mocha is constructing the test suite. In brief, you should keep in mind that the callbacks that are passed to describe are evaluated before the tests start. The callbacks passed to the it calls are not evaluated until the individual tests are executed. (See another answer of mine for all the gory details of what happens when.) So at the stage where api.base.satus.without_route is evaluated, api.base has not been set.
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', function () {
api.base.status.without_route();
});
});
Given the code you had in your question, I've assumed that api.base.status.without_route is a function which would fail if your conditions are not met. That it be a function looks peculiar to me but I don't know the larger context of your application. I'm basing my assumption on the fact that the 2nd argument passed to it should be a callback. If it is not a function, then you'll need to write whatever specific test you need (if (api.base.status.without_route === ...) etc.)

Implementing logging in Metro Application developed using Html/WinJS

I need to provide with error logging in my Windows 8 Metro application developed in Html/WinJS
so that user can get to know what went wrong from a log file located in the app's local folder.
I have checked WinJS.log(message, tags, type); which will write to the console but not able to find anything via which i can get it on a local file.
What is the best way to do the same and if there are any 3rd party libraries/js available for error logging in metro applications developed in WinJS ?
Thanks in advance.
WinJS.log is just a placeholder. Without proper initialization it does nothing (in fact, it's not set at all). If you just call WinJS.Utilities.startLog() at your application startup, it defaults to wiring up a logger for the console.
If you want something more complete, you'll need to build it. I've built a small sample below.
function startFileLog() {
// choose where the file will be stored:
var fileDestination = Windows.Storage.ApplicationData.current.localFolder;
var logger = new WinJS.Promise(function (complete) {
var logfilename = new Date().toISOString().replace(/[:-]/g, "");
logfilename = "log-" + logfilename + ".log";
fileDestination.createFileAsync(logfilename,
Windows.Storage.CreationCollisionOption.generateUniqueName)
.done(function (file) {
complete(file);
});
});
var actionFn = function (message, tag, type) {
logger.then(function (file) {
var m = WinJS.Utilities.formatLog(message, tag, type);
Windows.Storage.FileIO.appendTextAsync(file, m).done();
});
};
WinJS.Utilities.startLog({ action: actionFn });
}
By calling the startFileLog function above, it creates a new log file (by using the current Date/time as part of the file name) within a promise. Then, a function called actionFn is passed to the startLog function. By passing an optional property of the options named action, the default "write to console" behavior is overwritten (if you didn't want it overwritten, you could call startLog without the action, then copy the function reference from WinJS.log and replace it with your own function, and call it as well). When the log function is called, it now calls actionFn which uses the promise created earlier to verify that the log file is in fact available for writing before continuing. If it's not ready yet, it will be queued. So, this means that even though the file may not be ready immediately, the log will, in the end, contain the results you'd expect. There would be a short period of time where, due to async nature of WinJS, if the application crashed before the file completely opened, that logged items will be missed. You could delay the application startup if you wanted until the file was opened by returning the logger promise:
function startFileLog() {
/// ... etc..
return logger;
}
startFileLog().then(function() {
// the application can now be assured that the log file is ready to accept
// writes ... (but again, it's all async, so a write may be missed in
// extreme cases)
});
You'd likely want to create a function at the end of your application to clean/close the log file.

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