The last few projects I discussed were to get you started coding in ES6 JavaScript
. I intentionally kept the functionality very limited. In this article, I will show how to set up a test framework using my ES6 CLI Promises repository, promises
. There are many different frameworks to choose from. A simple one to set up is Mocha
.
Setting up
First, clone promises
(the instructions for cloning are included in the article that goes with this repository, My JS Journey: Promises).
Next, install the node
dependencies.
npm install
Then, build and run it to ensure everything is set up correctly.
npm run build npm start
It should say,
hello world
Install Mocha
Install Mocha.
npm install mocha --save-dev
This should have added mocha
to the devDependencies
in package.json
. Now is a good time to change the name
.
{ "name": "MochaExample", "version": "1.0.0", "description": "Simple Mocha example", "main": "index.js", "scripts": { "build": "babel src -d dist", "start": "node dist/index", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.24.0", "babel-core": "^6.24.0", "babel-preset-env": "^1.3.2", "babel-register": "^6.24.0", "mocha": "^3.3.0" } }
Might as well check in the code.
Convert src/hello.js to Implement a class
To facilitate exporting, I find it easiest to use classes. Modify src/hello.js as follows.
class Hello { static getWorld() { return new Promise((resolve) => { setTimeout(() => resolve(' world'), 500); }); } static hello() { return new Promise((resolve) => { this.getWorld().then(result => { resolve('hello' + result); }); }); } } export default Hello;
Notice that the functions changed from const
to static
. Also, now that they are part of the object, to reference them within the object, it is necessary to prefix them with this.
, e.g. this.getWorld()
, as in line 10.
Modify src/index.js
to accommodate the new class.
import Hello from './hello'; const sayHello = (message) => { console.log(message); }; const main = () => { var args = process.argv; if (args[2] && args[2] === '--v' ) { console.log('Es6CliSkeleton - version 1.0.0'); } else { Hello.hello().then(res => sayHello(res)); } }; main();
The lines that changed are 1 and 12, both of which reference the new Hello
class.
Build and run to ensure everything still works.
Adding Tests
First, create a folder test
. Add a file to this folder called hello_test.js
.
Add the first test.
import assert from 'assert'; import Hello from '../src/hello'; describe('Hello', function() { describe('getWorld', function() { it('should return space world', function() { Hello.getWorld() .then(result => { assert.equal(' world', result); }); }); }); });
Next, in package.json
, modify the “scripts” section’s “test” to:
"test": "mocha --compilers js:babel-core/register test/test.js"
Build and run the test.
npm run build npm run test
You should get output something like this.
> MochaExample@1.0.0 test /Users/your_src/promises > mocha --compilers js:babel-core/register test/*_test.js Hello getWorld ✓ should return space world 1 passing (100ms)
With a nice green checkmark.
Check the code in.
A Second Test
We should have a test on the other function, Hello.hello()
. It will be very similar the first one. Add this right code after the other test.
describe('hello', function() { it('should return hello world', function() { Hello.hello() .then(result => { assert.equal('hello world', result); }); }); });
Build and run. The output should look like this.
Hello getWorld ✓ should return space world hello ✓ should return hello world 2 passing (65ms)
Go ahead and check that in since the next section covers something a little different.
A Broken Promise
Let’s see what happens when the promise is broken. Modify what the promise returns in Hello.getWorld()
from
setTimeout(() => resolve(' world'), 500);
to
let input = "myErr"; if (input === "myErr") { reject(new Error(input)); } setTimeout(() => resolve(' world'), 500);
Build and run the app.
> MochaExample@1.0.0 start /Users/your_src/Closet/promises > node dist/index (node:61097) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 42
It fails, kind of how you might expect. Run the tests.
Hello getWorld ✓ should return space world (node:61087) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: myErr hello ✓ should return hello world (node:61087) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): Error: myErr 2 passing (111ms)
Interestingly, the tests pass, although they report the unhandled promise rejection. Let’s fix that first. Modify the tests to include a catch, which will also require the use of done
in lines 6, 10 and 14, and 19, 23 and 27.
import assert from 'assert'; import Hello from '../src/hello'; describe('Hello', function() { describe('getWorld', function() { it('should return space world', function(done) { Hello.getWorld() .then(result => { assert.equal(' world', result); done(); }) .catch(err => { console.log(err); done(err); }); }); }); describe('hello', function() { it('should return hello world', function(done) { Hello.hello() .then(result => { assert.equal('hello world', result); done(); }) .catch(err => { console.log(err); done(err); }); }); }); });
Now, the tests fail and let you know why:
1) Hello getWorld should return space world: Error: myErr at src/hello.js:5:39 at Function.getWorld (src/hello.js:3:12) at Context.<anonymous> (test/hello_test.js:7:13) 2) Hello hello should return hello world: Error: myErr at src/hello.js:5:39 at Function.getWorld (src/hello.js:3:12) at src/hello.js:12:12 at Function.hello (src/hello.js:11:12) at Context.<anonymous> (test/hello_test.js:20:13)
Remove the changes to getWorld()
. Check in the code.
In Conclusion
I generally start by writing my tests (Test Driven Development or TDD). I believe it is one of the fundamental practices in good code development. It is important to ensure you have complete test coverage. In this example, you can see I had tests for the “happy path” but had not covered potential failures. It was a bit contrived, but be on the lookout for things that can go wrong. In My JS Journey: Creating an npm Package, I show how to wrap Node fs
, which is their file system interface. That blog post includes more about how to use Mocha
in a less-contrived project. As that project progresses, it will have a lot of good examples where you need to test for things that might go wrong—a missing file, an empty file, invalid permissions—lots of things to test for. It also has
You can find the code for this project in my github
repository, mocha-example.
Copyright ©2014-17 Ramona Ridgewell. All rights reserved.
Pingback: My JS Journey: Creating an npm Package | RamonaRidgewell