27 - # of things I learnt by writing 1 testable line of code in Node.js

Disclaimer: I’m a level 0 (zero — thank you Medium for making ‘O’ and o look the same) in JS and Node and most of my Googling might reflect that. I’m a decade old Java dev. So assume everything in the JS/Node world is new to me. I didn’t go into JS learning per se except where I found the learning to be little more than syntax.

Learning 1 — ES6: Google told me that ES6 is the latest spec of JS and as late as 2015, ES7 is not yet out. So I read a few tutorials and tried on plain old JS without Node. I liked the class construct, but it’s the age of FP. How to write good functions in ES6?


Learning 2 — Arrows: Of course. I can follow (fat?) arrow functions and why they are better (correct binding of this and no more evil stuff with bind() or that=this even though I can’t claim to know why they are evil). What I got from all the reading was unless you want to get to the dark side, write arrow functions.


Learning 3 — Parenthesis in arrow functions. Here is how many ways you can define an arrow function . I’d also recommend to read https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/

From MDN

Or, much more simply

Thanks to Getify https://github.com/getify/You-Dont-Know-JS/blob/master/es6%20&%20beyond/fig1.png

Learning 4 — Learn with open source: I highly recommend javascripting and learnyounode. Thanks to these, I got the syntax fairly quickly and also played around with the code until I got comfortable. I Wrote an arrow function to add numbers from command line. Ran it, console.log pretty printed everything nicely and I thought Eureka!! and I know kung fu!!


Learning 5 — Modular code: Like the not-so-good dev that I’m, I wanted to write a test case after I wrote the code — no TDD and that’s why I’m not-so-good dev. I need to extract a function out of the code in one file and call it from another, so as to make it testable. Off I go Googling and I found that I can use export defaultin ES6.

//file name is arg-sum.js
export default
add = (x) => {
const output = x.reduce( (a, b) => Number(a) + Number(b), 0);
return output;
}

Learning 6 — Import what you have exported: Wrote export default my-beloved-add. Created another script, wrote import considering it’s the antonym for export. And Bham!

// file name runner.js
import adder from 'arg-sum';
console.log(adder.add(process.argv.slice(2, process.argv.length )));

And get greeted with

(scratch head for a minute and ask Google).

Learning 7 — First of all, ES6 is actually ES2015 and ES7 is ES2016.


Learning 8 — Node and ES6: Second of all, Node doesn’t fully support ES6 syntax and they use something called CommonJS underneath. Check out this thread in SO to understand the quirks.

Quoting the SO answer: At the time of writing this (answered Oct 27 ‘16), no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS.


Learning 9 — Transform ES6 to ES5: And that we should use a transpiler to compile our ES6 code into ES5 code.


Learning 10 — Babel is the transpiler. I move over to Bable, sure enough — they claim to transpile so that everyone can understand ES6 code including Node.


Learning 11 — NPM. Even for single function, now we have ourselves a dependency on another module/lib/package. Node has a package manager called Node Package Manager. NPM for short. For installing Babel, we’d do something like npm install babel in your project directory (you might want to do a npm init first.


Learning 12 — save, save-dev, save-but-don’t-tell-anyone: NPM has 4 flags for installing any package — —-save-dev, —-save, <default> and -g(for all projects in your laptop globe). To see when to use what, check this post. Once you have read that, leave a comment if the default option seems weird.


Learning 13 —Press g for global destruction: BTW, Don’t install development-only NPM modules globally, because that’ll like totally screw up your system.

Now we installed Babel to transpile ES6 to ES5 so that we can have export default and import working. Wait, how do we tell Node to use Babel or other way around?


Learning 14 — babel-node: Babel is available for both Front end and Backend. There is a babel-node CLI that let’s you use Babel CLI for Node. So, instead of saying node run.js you can say babel-node run.js (a nice little thing to have).


Learning 15— BUUUUUUUUT…. it shouldn’t be used in production (according to their documentation) and ES6 module loading might not work.

Even though I’m not going to deploy my add function to Production, why risk it?

Okay.. plain old Babel it is then. But wait, there’s more.


Learning 16 — Babel? Preset!: The installation for Babel also asks me to install a preset as well. I couldn’t find what a preset is easily on the website.

At this point, I am thinking of pulling the plug on Babel and wondering if it’s easy to stick to ES5 (and on-demand figuring out of Node-supported-ES6).


Learning 17 - module.exports: Back in the Babel-less Node world, we can export a module, the CommonJS way (or one of many ways). Same code from above, but like below:

function add(x) {
const output = x.reduce( (a, b) => Number(a) + Number(b), 0);
return output;
}
module.exports = {
add: add
}

Or, another way:

Learning 18- exports: This would also work.

exports.add = (x) => {
const output = x.reduce( (a, b) => Number(a) + Number(b), 0);
return output;
}

Learning 19: But not both module.exports and exports together: This is why.

Learning 20 — require: If you export (as in ES6), you import. If you exports (as in CommonJS), you require.

//filename is runner.js
const
adder = require('./arg-sum');
console.log(adder.add(process.argv.slice(2, process.argv.length )));

Learning 21 — ../../../ or ../.. ? If you notice, when I wrote the require, I had to tell CommonJS (or Node in general) which module, or file loosely talking, we are requiring. In this case, my file name was arg-sum and it was in the same directory as the runner. What if you have nested directory structure and you are requiring all over the place(which you shouldn’t do)?

One answer, from one of the guys i know, is to have a index.js file per directory that imports all necessary files and you just export the index file. I found this to be clunky. But anyway, that’s a problem for Node at scale.

After all this, we now have an adder we can test.


Learning 22- Mocha: Back in the Java world, JUnit is all you need. It’ll organise your tests, run them, assert the output and tell you what failed. In JS world, we have a test runner (which is Mocha). We can then plug in any assertion library we want — power to us and all. In fact, people are using these assertion libraries so much that if you Google, Mocha assertion, the first suggested link is Chai. So, first we go with npm install mocha (save-dev)


Learning 23-Chai

Mocha has 5 assertion libraries that we can use:

But Chai is the most popular one (by popular I mean, everyone I know uses it). So, npm install chai --save-dev.

Now that we got everything we need, we will go ahead and check 1+2+3===6. But how do we check (assert) our output is the same as the expected output?

Learning 24-Should expect asserts:

From http://chaijs.com/

The should and expect feels natural to write test cases and reads well too.

Learning 25 — The parenthesis again: Did you notice how should is chai.should() but expect is chai.expect ? Particularly, did you notice the missing () for expect? Chai documentation calls out this difference, which was helpful. But I couldn’t understand why though.

At this point, my test file looked like this:

let chai = require('chai');
let expect = chai.expect;
let adder = require('../../src/arg-sum/arg-sum');

describe('test-add-arg-sum', function () {
it('should return the addition result of 2 valid numbers', function () {
const x = adder.add([1, 2, 3]);
expect(x).to.equal(6);
});
});

(Once again, the require file path navigates out of my test folder and into my src folder. And no, I’m not welcoming comments on the naming).


Learning 26-Functional vs imperative: Apart from these, ICYMI, I also observed two additional bits: The adder takes in CLI arguments and then sums them up. But we are not doing a for or for-each. We are using reduce. While for-each is kinda imperative, what stuck me was in all of our for loops, we are doing one of three things:

  • loop through the elements and do one operation per element.
  • loop through the elements to selectively do an operation based on some condition (filter items).
  • loop through the elements to produce a cumulative final single output (add, subtract, multiply, aggregation, etc).

In JS, we have methods for all of these and they are functions. In the order above, we have map, filter, reduce. Please read this for more details if you are new FP.

Learning 27-One last thing that I found odd was the CLI arguments passed to a Node program. First 2 params of Node command line arguments are node path and program name. For example,

console.log('process args', process.argv);
//prints the following
process args [ '/usr/local/bin/node',
'/Users/myuser/node_learning/src/arg-sum/runner.js',
'1',
'2',
'3' ]

While I don’t understand why this is so, I know for sure that there is a use case which I don’t probably see at the moment.

Conclusion

I wanted to highlight how many little things we learn by writing just a few lines of code. While some/all of this might be child’s play for a seasoned Node developer, I found each of the above points to be new and unique and wanted to share the links that I found useful along the way. Thanks for reading this through!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.