To gain a better understanding of the decisions knockoutjs made in terms of implementation, I decided to try and build my own version. I named it
sea as a homonym for ‘see’ which is somehow related to observable properties, I swear. I started with a simple HTML page that basically contained a series of “things that should work”. The first was a simple
div that followed the mouse cursor using two observables to track position.
After a few more examples similar to that, I realized this library had two testable aspects: the frp parts, which can exist in any JS enviroment, and the data binding parts, which require a DOM. I decided to formalize and write some actual tests using mocha, because I’d heard you could use it in a browser and node.
And that’s when things got difficult. Sure, mocha supports browser-based tests, but it leaves the part about getting your tests into the HTML harness up to you. You could just load a single JS file, but I bet your library is a bit more complex than just one file, and you’re probably using a module system to help keep things… modular (such as
assert library). So if you’re using requirejs or browserify, it’s up to you to write a build step to make it easy to consume the tests.
I also looked into using phantomjs or one of myriad modules that purported to easily get mocha working in phantomjs. The problem is that they all assume a build system (like grunt), assume you’re writing non-
requireable code, or assume you’re using requirejs. Then there’s the problem of getting the test output… out… of phantomjs. So for now, that will wait.
There was one more problem that greatly exacerbated my troubles: I wanted to use the
exports interface that mocha provides. Every example I’ve seen of using mocha in a browser uses either the tdd interface or the bdd interface. These are especially suited for the browser because it’s relatively easy to expose the interfaces globally in the browser environment, while the
exports interface requires a more complex shim. I find both of them a little verbose, expecially after writing so many tests for vash using vows.
Here is the typical bdd interface:
That’s fine, but I find the repeated
it distracting. Here’s the same using the
Honestly, it’s basically the same, I know. For more complex suites I’ve seen it get very difficult to read, but at this point I’m mostly arguing a personal preference.
Final goals for this testing environment:
- Write “mocha” tests using the
- Be able to
requirethe in-progress library, along with anything else needed, like
assertor chaijs or sinonjs
- Write the tests without caring if they’ll be running in a browser or node
Step 1: Project Structure
This is a simple project, and contains the following files:
/index.js # frp-parts, node-only /dom.js # data binding, requires DOM /package.json # typical, but will have build/test commands /test/ runner.html # the mocha test harness test.sea.js # the test, run using mocha(1) or in runner.html
Using the library is simple:
And using the data-binding components (assumes a DOM):
If you’ve ever used knockoutjs, then this should look very familiar. I changed the syntax a bit for data-binding to simplify my job (I didn’t want to write a full parser, so instead of using a single
data-bind attribute, each
data- attribute is matched against a valid registered binding).
Step 2: The Tests
The node tests look like (abbreviated):
And the DOM tests (abbreviated):
For the most part, writing a test for the browser or node is exactly the same in terms of structure. Obviously the browser test won’t run in node because of lack of DOM objects, but the structure of the tests are the same.
Step 3: Bundling
Next we need to bundle the tests so that
require works. It’s pretty easy, but took a bit of fiddling with
browserify to figure out:
node_modules/browserify/bin/cmd.js ./test/test.dom.js --standalone tests > test.bundle.js
- Runs browserify on
test/test.dom.js, which spiders through and includes the dependencies, including
assertand other things (like the
processshim and such).
- Uses the
--standaloneflag with argument
tests, which wraps the bundle in a UMD guard under the name of ‘tests’. In the absense of a
requirefunction in the target environment, the bundle will be attached to the global window as
- Dump the resulting output into
The key step is #2, otherwise there would be no way to reference the tests from the test runner, which is integral when using the
exports interface. Browserify is really good at script interop, allowing you to explicitly control what is exported.
Step 4: Hacking the Harness
The last step is to do something super hacky: copy mocha’s
exports interface, modify it slightly, and place it into the typical test harness:
The key here is that I’m manually passing in
tests, defined by our test bundle, into a modified version of mocha’s
exports interface. The only modifications were to access mocha using the global
Mocha, and to be able to pass in the exports object and suites, instead of relying on mocha’s
Step 5: Script it
I could go with a build system, like grunt or a Makefile, but that’s too much for this tiny project. A few simple additions to the
scripts field of my
package.json will do fine:
If you use
require and browserify, you really don’t need a true build system. The worst is having to manually specify files for inclusion in said build system, and
require takes care of that.
Even simple projects quickly get complex when you want to be able to test in a browser and node. With the power of browserify and mocha, a lot of the hard things are taken care of, leaving just a little bit of undefined but required glue. Hopefully this helps the next time you start a small project and want some tests!