I've been trying to add unit tests for some modular ES6 code. I have a project structure like this:
project
└───src
| └───js
| cumsum.js
| index.js <--- entry point
└───test
tests.js <--- QUnit test code
This is what's in cumsum.js:
export const cumsum=x=>{
var result = x.reduce((r, a)=> {
if (r.length > 0) {
a += r[r.length - 1];
}
r.push(a);
return r;
}, []);
return result;
}
Now, if I run this sample test by running qunit in the command line, it will work:
const A=[1,2,3,4,5];
const expected=[1,3,6,10,15];
QUnit.test( "cumsum", function( assert ) {
assert.deepEqual([1,3,6,10,15],expected);
});
but if I try to import the actual cumsum function, it doesn't recognize proper ES6 import syntax:
import {cumsum} from '../src/js/cumsum';
const A=[1,2,3,4,5];
const expected=[1,3,6,10,15];
QUnit.test( "cumsum", function( assert ) {
assert.deepEqual(cumsum(A),expected);
});
I just get the error
SyntaxError: Unexpected token {
Is there a way to use QUnit with ES6 modules? If not, is there a unit testing framework that will let me test these modules?
Here's what I've come up with so far.
Chrome can sort of natively run ES6 modules. It's not good enough for web production but it is enough to run some unit tests. So in the test folder I have index.html like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<script type="module" src="../src/js/cumsum.js"></script>
<script type="module" src="tests.js"></script>
</body>
</html>
In test/tests.js I have the original test code:
import {cumsum} from '../src/js/cumsum';
const A=[1,2,3,4,5];
const expected=[1,3,6,10,15];
QUnit.test( "cumsum", function( assert ) {
assert.deepEqual(cumsum(A),expected);
});
Now, for some reason you can't directly open test/index.html in the web browser because although Chrome will happily read ordinary javascript files locally it will break if you set type="module" on a local file. Instead we have to launch a web server and view it that way. Any dev server will do, so webpack-dev-server works fine. Open http://localhost:8080/test/ in Chrome and the unit tests load.
Does anyone have a better way of doing this? Node.js uses the same javascript engine as Chrome so in theory I think it should be possible to do this from the command line without launching a web server and opening a browser.
Related
I am just getting started with TypeScript (and front end development in general) coming from a c# background so sorry if this is a really basic question, but I can't figure out where I'm going wrong...
What I'm trying to do for now is create a really basic program to retrieve some sample data from a url in JSON format, parse to TS classes, and display it on the page.
In order to get the json response I found this answer that recommends using a node package. I got it installed and it seems to be ok (at least TS doesn't give me any errors).
I also figured out that I need to compile (not sure if that's the right term?) with Browserify to make it browser compatible since it's using a node module. I did that but now when I try to run in a browser it's telling me my method is not defined.
export class Keynote {
KeyValue: string;
Description: string;
Parent: string;
}
Retrieval class is:
import {Keynote} from "./Keynote";
import * as request from "request-promise-native";
function GetKeynotes(): Array<Keynote> {
const baseUrl = 'https://revolutiondesign.biz/Sandbox/TypeScript/KeynoteProvider.php';
var options = {uri: baseUrl};
const result = JSON.parse(request.get(options));
return result;
}
and html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Keynotes Testing</title>
<script src="bundle.js"></script>
<script>
function Retrieve() {
var notes = GetKeynotes();
document.getElementById('container').innerText = JSON.stringify(notes);
}
</script>
</head>
<body>
<div>
<button content="Get some notes" onclick="Retrieve()">Get some notes</button>
</div>
<div id="container">
</div>
</body>
</html>
Browserify is really long so I didn't want to copy here but you can see it at https://revolutiondesign.biz/Sandbox/TypeScript/KeynoteDisplay.html in the source if you want.
When I click the button I get this error in the browser:
KeynoteDisplay.html:9 Uncaught ReferenceError: GetKeynotes is not defined
at Retrieve (KeynoteDisplay.html:9)
at HTMLButtonElement.onclick (KeynoteDisplay.html:16)
Retrieve # KeynoteDisplay.html:9
onclick # KeynoteDisplay.html:16
GetKeynotes is defined in my typescript, and on the 5th line of bundle.js I see a function with that name... Why is it undefined?
UPDATE
Ok I have played with jspm and SystemJs but I still don't have something right. I referenced the module with jspm and did a bundle to build.js and uploaded the whole thing just to make sure everything is there. Here are the tags in my html for scripts:
<script src="../../jspm_packages/system.js"></script>
<script src="../../config.js"></script>
<script src="build.js"></script>
<script>
System.import("Sandbox/TypeScript/build.js")
function Retrieve() {
System.import("Sandbox/TypeScript/build.js")
var notes = GetKeynotes();
document.getElementById('container').innerText = JSON.stringify(notes);
}
</script>
When I press the button I can debug in my function, but it still gives the error, 'GetKeynotes is not defined' just like before... Again I can see a function with that name in the build.js file so I don't understand why it's not finding it.
I also tried System.import("Sandbox/TypeScript/KeynoteRetrieval.js") but it gives the error:
Uncaught (in promise) Error: (SystemJS) Node tls module not supported in browsers.
Error loading https://revolutiondesign.biz/Sandbox/TypeScript/KeynoteRetrieval.js
I have set-up a little test environment for a project. It should use mocha and chai for unit testing. I've set up a html file as test runner:
<!DOCTYPE html>
<html>
<head>
<title>Mocha Tests</title>
<link rel="stylesheet" href="node_modules/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="node_modules/mocha/mocha.js"></script>
<script src="node_modules/chai/chai.js"></script>
<script>mocha.setup('bdd')</script>
<script src="test/chaiTest.js"></script>
<script>mocha.run();</script>
</body>
</html>
The chaiTest.js file continas this simple test:
let assert = chai.assert;
describe('simple test', () => {
it('should be equal', () => {
assert.equal(1, 1);
});
});
When I now call the test runner in my browser, the results are shown correctly. It works fine. But when I run mocha on console, it tells me that chai is not defined.
So to get this working in console, I just add a require of chai in the fist line of the test file.
let chai = require('chai');
Now the test runs fine in console, but when I execute tests in the browser, it tells me that require is undefined.
I know, these errors totally makes sense here! They are undefined. But is there a way to write tests with mocha and chai and let them execute in browser and console?
I know I could create two test files, for browser and console. But that would be hard to maintain. So I would like to write a single test file, executing correctly in both environments ...
I found a solution by myself now. It's needed to use a configuration file for chai. Like in my case, I called it chaiconf.js. In this file can be written a default setup of chai. This file will be required before each tests.
My chaiconf.js:
let chai = require("chai");
// print stack trace on assertion errors
chai.config.includeStack = true;
// register globals
global.AssertionError = chai.AssertionError;
global.Assertion = chai.Assertion;
global.expect = chai.expect;
global.assert = chai.assert;
// enable should style
chai.should();
Now append this configuration to every test. For this create a script entry in the package.json:
"scripts": {
"test": "mocha --require chaiconf.js"
},
Now, whenever you use npm test in console, the chaiconf.js will be required before tests and make chai globally available, like in the browser.
Another way without a configuration file would be to use an inline decision to receive chai:
let globChai = typeof require === 'undefined' ? chai : require('chai');
I am trying to use express-babelify-middleware
with FeathersJS and the error shows up in the browser console:
ReferenceError: main_run is not defined
I take this to mean that babelify is not working or I am using it incorrectly as main_run is in the global namespace of the src in my html file.
Here is my setup using the structure from feathers generate:
public/index.html:
<!DOCTYPE html>
<html>
<head>
<title>babelify test</title>
<script src="main.js"></script>
<script>
main_run()
</script>
</head><body>
<p>Testing feathers with babelify</p>
</body></html>
public/main.js
const external_module = require('./test')
function main_run(){
external_module()
}
public/test.js
module.exports = function(){
console.log("Hello world for an external module")
}
among the .uses of src/app.js:
...
const babelify = require('express-babelify-middleware')
...
app.use(compress())
.options('*', cors())
.use(cors())
//the line that is not working:
.use('/main.js', babelify( path.join(app.get('public'), 'main.js') ))
.use(favicon( path.join(app.get('public'), 'favicon.ico') ))
.use('/', serveStatic( app.get('public') ))
When I visit localhost:3030/main.js I can see the file, but the functions look to be in a function of their own, so I don't know how to get into that function.
Silly problem, one can't access browserified code in the html file that calls it. So public/index.html can't access main_run unless it is attached to the window object. There is a similar question
here.
Other than that, my code works perfectly.
In main.js place the following code at the bottom:
window.main_run = main_run
Then in index.html replace the main_run() line with:
window.main_run()
This will write the contents of test.js to the console.
I used the yeoman webapp-generator to create a fancy website template. It creates a test-folder and scaffolds the whole project incl. one simple unittest. To try the phantomjs functionality I added an additional function:
describe("DOM Test", function () {
var el = document.createElement("div");
el.id = "myDiv";
el.innerHTML = "Hello World!";
document.body.appendChild(el);
var myEl = document.getElementById('myDiv');
it("has the right text", function () {
(myEl.innerHTML).should.equal("Hello World!");
});
});
But when I run grunt test I always get this annoying error:
Running "mocha:test" (mocha) task
Testing: test/index.html
Warning: PhantomJS timed out, possibly due to a missing Mocha run() call. Use --force to continue.
Aborted due to warnings.
My mocha-entry within the Gruntfile looks like this (its a slightly modified version of the generated one. I replaced the url by a relative path with wildcard):
mocha: {
test: {
src: ['test/**/*.html'],
}
},
And the test/index.html looks like this:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Mocha Spec Runner</title>
<link rel="stylesheet" href="bower_components/mocha/mocha.css">
</head>
<body>
<div id="mocha"></div>
<script src="bower_components/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="bower_components/chai/chai.js"></script>
<script>
var assert = chai.assert;
var expect = chai.expect;
var should = chai.should();
</script>
<!-- include source files here... -->
<!-- include spec files here... -->
<script src="spec/test.js"></script>
<script>
if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
else { mocha.run(); }
</script>
</body>
</html>
And I tried the following things (without success):
run bower twice (suggested by this guy)
added grunt.config.set('server.port', 7002) (suggested by this github issue post)
Go to your test folder and run bower install:
cd test
bower install
Then try again to run grunt test.
You have two bower_components folders, one in root and one in test.
i want to use a config file in nodejs and in a web javascript.
config.js:
var conf = {};
conf.name = 'testname';
conf.pass = 'abc123';
conf.ip = '0.0.0.0';
conf.port = 100;
conf.delay = 5;
exports.config = conf;
use it in nodejs with:
var conf = require('config.js');
console.log(conf.config.name);
want to use this same file inside html but how? I was thinking by this way but i don't know how to use it in web. When i try to use it in web i get Reference error: exports is not defined.
config.html:
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<script src="./config.js"></script>
<script>
var cnf = conf;
function getCnf(){
alert(cnf.config.name);
}
</script>
</head>
<body>
<button onclick="getCnf();">test</button>
</body>
</html>
Anyone know how i must change config.js to use it in both systems nodejs and web?
PS: Webside is running on nodejs http npm module.
You can put a condition around that, like this
if (typeof module !== 'undefined' && module.exports) {
module.exports.config = conf;
}
This makes sure that you have module and exports are available before setting any value on exports.
Note: exports is just another variable referring module.exports. So, they both are one and the same unless you assign something else to either of them. In case, you assign something to either of them, whatever is there in module.exports will be exported in Node.js. You can read more about exports in this blog post
Thanks, that typeof was all i needed.
#Phoenix: I know that there is a way to do that but that's not necessary. The variable are only used for some ajax requests and deley timers later.
You can use browserify to bundle your CommonJS for the browser without resorting to environment switches.
Install browserify using npm i browserify -g
Bundle your config.js and export it for external use with the -r tag
browserify -r ./config.js -o bundle.js
Include the bundle in your code and use it:
<!doctype html>
<html lang="en">
<head>
<title>Document</title>
<script src="./bundle.js"></script>
<script>
var cnf = require("./config.js");
function getCnf(){
alert(cnf.config.name);
}
</script>
</head>
<body>
<button onclick="getCnf();">test</button>
</body>
</html>