How To Test a Grunt Plugin - javascript

I'm creating my first ever Grunt Plugin. I would really like to practice testing my plugin as I develop. I've selected mocha as my testing framework, as it seems popular, and setup my gruntfile to automatically watch when test files are altered and run them. This all looks good.
But, I've not found a lot of documentation on how to actually test a Grunt Plugin. I've looked at code of about a dozen different grunt plugins, especially the contrib ones, and they don't make a lot of sense to me.
As I try to test my code I'm trying to break things down into very specific functions. So, here is the basic structure of a plugin, with a function in it.
'use strict';
function testOutsideOfTask(){
return "i am outside";
}
module.exports = function(grunt) {
grunt.registerMultiTask('example_task', 'I do a thing.', function() {
function testInsideOfTask(){
return "i am inside";
}
});
};
I've included a couple of methods just explicitly to make sure my testing is working, and it's not. Neither of these methods seem to be accessible... How could I access both of them for testing? Here is the mocha test as I have it.
var grunt = require('grunt');
var assert = require("assert");
describe('testOutsideOfTask', function() {
it('do something', function() {
assert.equal("i am outside", testOutsideOfTask());
});
});
describe('testInsideOfTask', function() {
it('do something', function() {
assert.equal("i am inside", testInsideOfTask());
});
});
They both return something like this. So, somehow it just can't access the functions, however, when I look at other testing examples, they don't seem to specifically require the file that is being tested... For example https://github.com/gruntjs/grunt-contrib-clean/blob/master/test/clean_test.js
1) testOutsideOfTask should do something:
ReferenceError: testOutsideOfTask is not defined
Thanks very much!

You want to be testing functionality, ideally. Your Grunt plugin should end up as a wrapper around more abstract methods that do what you want but not necessarily tied to how Grunt works; then for the actual plugin you run those methods on each file, log some output etc. Something like this:
var lib = require('./lib.js');
module.exports = function(grunt) {
grunt.registerMultiTask('test', function() {
lib.doSomeMethod();
grunt.log.ok('Something happened');
});
}
So in that pseudo code you would want to actually test the doSomeMethod function and not that Grunt registered a task or logged to the CLI. That's already well tested.
A lot of Grunt plugins work in this way, in fact many are wrappers around existing node modules. For example, grunt-recess is a wrapper of twitter/recess:
https://github.com/sindresorhus/grunt-recess
https://github.com/twitter/recess
One of my own modules is more Grunt specific, but the tests for that focus on the actual functionality of the module. You can have a look at that here:
https://github.com/ben-eb/grunt-available-tasks/blob/master/test/lib/filterTasks.test.js
I'm also testing with Mocha. I'd recommend that you use grunt-mocha-test to run your tests through Grunt (as well as JSHint and others).

Related

Global function not available in Window object when using Jasmine & Headless Chrome

I'm having a hard time figuring out how I can access a function that is usually available as a method of the window object in the browser. I'm using Karma, Headless Chrome and Jasmine.
Here is a simplification of my issue:
I have a module called add-numbers-together.js:
function addTogether(a, b) {
return a + b;
}
(function(){
addTogether(2,5);
})();
That is being tested by this Jasmine test:
describe('add-numbers-together.js', function() {
it('Should add numbers together', function() {
require('module/add-numbers-together');
console.info(window.addTogether);
});
});
The require statement is definitely retrieving the module ok, I have tested that.
I was expecting the console.info to print out the definition of the function, as is the case when I do this in an actual browser, but instead this console.info returns undefined. Why is this? and how can I access the addTogether function after the module has been required in the test?
I'm guessing this is some quirk of Jasmine or Headless Chrome but I can't find the answer anywhere no matter how hard I search!
Also, please note: I do not want to add any test code to the module itself, that is not an option for me.
I changed the test slightly to check that the function is not undefined, but it failed regardless of Chrome or ChromeHeadless as the browser.
describe('add-numbers-together.js', function() {
it('Should add numbers together', function() {
require('module/add-numbers-together');
expect(window.addTogether).not.toBeUndefined();
});
});
To get a version of this test passing, you have several options:
Make the code under test a real module and export the function, then change the test to import that module (recommended). This can also be define and require if using AMD.
Configure your build setup to force a global export from that module without changing the code (webpack can do this, and it's kind of hackey, but it works if you can't edit that script). This is usually only done with 3rd party scripts.
Put the script content in the test file above the test code (not recommended, since you are copy-pasting and changes won't be synced up).
Load the script under test and then the test in the fixture markup (works, but kind of lame since your test fixture HTML now hard codes a script reference).
Here is the code under test rewritten for option #1 with the export of the function.
export function addTogether(a, b) {
return a + b;
}
(function () {
addTogether(2, 5);
})();
And here is the test rewritten for option #1 with the import of the module under test.
import * as addNumbersTogether from 'add-numbers-together';
describe('add-numbers-together.js', function () {
it('Should add numbers together', function () {
expect(addNumbersTogether.addTogether).not.toBeUndefined();
});
});

Including external .js files for unit tests

I am trying to run coverage tests for my JavaScript unit tests (written in QUnit [not my idea, legacy project]) and run them via command line.
Problem i am facing is references to external bits of code.
Look at example bellow:
Code:
var DateHelper = function() {
return {
GetUtcDate: function (date) {
if (DateTypeChecker.Data["date"]) {
return new Date();
}
return date;
}
}
Test:
QUnit.test('GetUtcNow - compare UTC date', function(assert) {
var currentUtcDate = DateHelper.GetUtcNow();
var nowDate = new Date();
assert.equal(nowDate.getUTCFullYear() == currentUtcDate.getFullYear(), 'dates are the same');
});
So a very simple test that checks two dates, this works lovely when run in the browser, because the external javascript file containing DateTypeChecker is loaded in the HEAD.
But this test fails in the command line as node (or w/e is performing the test) doesn't have a reference to DateTypeChecker object.
My question is, how do i fix this, so that the file for DateTypeChecker is loaded/required? (i know that i can do this with RequireJS, but i don't wan't to add more dependencies and frameworks)
I think this is a general question for js unit testing (not just QUnit).
Two options: (a) mock out the external dependency or (b) actually include the external dependency JS file. For the second option (b), I would use a test runner like Karma and hook up QUnit to it. The configuration is pretty straight forward on that linked site.
The first option (a) is what I would recommend, however. It makes your tests more idempotent and thus loosely coupled. In QUnit you can set up a mock pretty easy, we just use a simple beforeEach function:
QUnit.module( "testing DateHelper", {
beforeEach: function() {
// before each test runs, we can create a "fake" DateTypeChecker
// Note that it doesn't matter what this thing does, so long as it
// satisfies your dependency in the DateHelper
window.DateTypeCheker = {
Data: { date: true }
};
}
});
QUnit.test( ... ); // your normal stuff

Accessing a Meteor Template Helper Function in Jasmine for Integration Testing

I'm trying to run Jasmine client integration tests on a meteor project. I'm using meteor 0.9.4, and the sanjo:jasmine package for Jasmine.
I have written a test which looks like:
describe("Template.dashboard.tasks", function() {
it("ela displays correct assessment", function() {
Session.set("selected_subject", "math");
Session.set('selected_grade', "1");
tasks = Template.dashboard.tasks();
expect(true).toBe(true);
});
});
I get an error before it can get to the end of the test:
Cannot read property 'tasks' of undefined
This means that Template.dashboard does not exist within the scope of this test.
Template.dashboard.tasks() is a helper function which works completely, and it is in a js file within a view folder. Regular Jasmine tests work as expected, but as soon as I try to use one of my own functions from another file, it doesn't work.
My question is: Is there something I need to do to give the Jasmine test access to my template helper functions?
In Meteor, Template helper functions used to be formatted like this:
Template.dashboard.tasks = function () {
...
};
But that has been deprecated, and the new format is:
Template.dashboard.helpers({
tasks: function(){
...
}
});
In Jasmine, with the previous formatting, you could access helper functions like:
Template.dashboard.tasks();
But now you must call helper functions like this:
Template.dashboard.__helpers[' tasks']();
Sanjo (the original author of the meteor-jasmine repo) suggested using a function like this to make it easier to call helper functions (especially if the syntax ends up getting changed again):
function callHelper(template, helperName, context = {}, args = []) {
template.__helpers[` ${helperName}`].apply(context, args);
}
An updated answer to this question for Meteor 1.3 (sorry I use mocha, but it does not affect the answer):
Template.foo and Template.foo helpers won't be eagerly set up when testing, so you need to import foo.html then foo.js.
Here is an example :
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Foo } from '/collections/foo.js';
import { assert } from 'meteor/practicalmeteor:chai';
import './foo.html'; // now Template.foo is defined
import './foo.js'; // now Template.foo.__helpers[' bar'] is defined
describe('foo handlers', () => {
it("Should test bar", () => {
// don't forget the space, helper key is ' bar' not 'bar'
var bar = Template.foo.__helpers[' bar'].apply({foo:"bar"},3);
assert.Equal(bar,'bar');
});
});
Of course as said before, you should definitely encapsulate the weird Template.foo.__helpers[' bar'].apply(context,args) into a nice, clean helper.
tests on server part are running nicely from start, and there is indeed one more thing to do in order to run tests on the frontend part. I'll try to find you that.
In addition, consider reading or reading again the famous and explicit article from Dr. Llama's Blog related to Jasmin/Meteor : Bullet-proof Meteor applications with Velocity, Unit Testing, Integration Testing and Jasmine

Reuse scenarios by using mocha

Recently I've started to use JS and mocha.
I've wrote some tests already, but now I got to the point when I need to reuse my already written tests.
I've tired to look for "it" / "describe" reusing, but didn't find something useful...
Does anyone have some good example ?
Thanks
Considering that if you only do unit testing, you won't catch errors due to integration problems between your components, you have at some point to test your components together. It would be a shame to dump mocha to run these tests. So you may want to run with mocha a bunch of tests that follow the same general patter but differ in some small respects.
The way I've found around this problem is to create my test functions dynamically. It looks like this:
describe("foo", function () {
function makeTest(paramA, paramB, ...) {
return function () {
// perform the test on the basis of paramA, paramB, ...
};
}
it("test that foo does bar", makeTest("foo_bar.txt", "foo_bar_expected.txt", ...));
it("test what when baz, then toto", makeTest("when_baz_toto.txt", "totoplex.txt", ...));
[...]
});
You can see a real example here.
Note that there is nothing that forces you to have your makeTest function be in the describe scope. If you have a kind of test you think is general enough to be of use to others, you could put it in a module and require it.
Considering each test is only designed to test a single feature/unit, generally you want to avoid reusing your tests. It's best to keep each test self-contained an minimize the dependencies of the test.
That said, if you have something you repeat often in your tests, you can use a beforeEach to keep things more concise
describe("Something", function() {
// declare your reusable var
var something;
// this gets called before each test
beforeEach(function() {
something = new Something();
});
// use the reusable var in each test
it("should say hello", function() {
var msg = something.hello();
assert.equal(msg, "hello");
});
// use it again here...
it("should say bye", function() {
var msg = something.bye();
assert.equal(msg, "bye");
});
});
You can even use an async beforeEach
beforeEach(function(done) {
something = new Something();
// function that takes a while
something.init(123, done);
});

How can i use sinon to stub functions for neo4j Thingdom module

I am having some issues writing some unit tests where i would like to stub out the functionality of the neo4j Thingdom module.
After a few hours of failed attempts i have been searching around the web and the only point of reference i found was a sample project which used to sinon.createStubInstance(neo4j.GraphDatabase); to stub out the entire object. For me, and becuase this seemed to a be a throw away project i wanted a more fine grained approach so i can test that for instance as the Thingdom API outlines when saving a node you create it (non persisted) persist it and then you can index it if you wish which are three calls and could be outlined in multiple specific tests, which i am not sure can be achieved with the createStubInstance setup (i.e. found out if a function was called once).
Example "create node" function (this is just to illustrate the function, i am trying to build it out using the tests)
User.create = function(data, next){
var node = db.createNode(data);
node.save(function(err, node){
next(null,node);
});
};
I am able to stub functions of the top level object (neo4j.GraphDatabase) so this works:
it('should create a node for persistence', function(){
var stub = sinon.stub(neo4j.GraphDatabase.prototype, 'createNode');
User.create({}, res);
stub.calledOnce.should.be.ok;
stub.restore();
});
The issue comes with the next set of test i wish to run which tests if the call to persist the node to the database is called (the node,save) method:
I am not sure if this is possible or it can be achieved but i have tried several variations of the stub and non seem to work (on neo4j.Node, neo4j.Node.prototype) and they all come back with varying errors such as can't wrap undefined etc. and this is probably due to the createNode function generating the node and not my code directly.
Is there something i am glaringly doing wrong, am i missing the trick or can you just not do this? if not what are the best tactics to deal with stuff like this?
A possible solution is to return a stubbed or mocked object, giving you control on what happens after the node is created:
it('should create a node for persistence and call save', function () {
var stubbedNode = {
save: sinon.stub().yields(undefined, stubbedNode)
};
var stub = sinon.stub(neo4j.GraphDatabase.prototype, 'createNode').returns(stubbedNode);
User.create({}, res);
stub.calledOnce.should.be.ok;
stub.restore();
stubbedNode.save.calledOnce.should.be.ok;
});
We couldn't do it directly, the way the module is setup it doesn't work to well with Sinon. What we are doing is simply abstracting the module away and wrapping it in a simple facade/adapter which we are able to stub on our unit tests.
As we are not doing anything bar calling the neo4j module in that class we are integration (and will validate when regression testing) testing that part to make sure we are hitting the neo4j database.

Categories

Resources