Unit Testing in ReasonML

Here's a way to create and run automated unit tests within the ReasonML ecosystem.

Young Ecosystem

Reason is a language based on OCaml, which is not young. Reason compiles to JavaScript via Bucklescript. That's not confusing. :) The Reason/JS/Bucklescript thing is particularly young, which seems obvious once you try to figure out the testing story.

There is no testing section in the Reason guides documentation. I eventually googled my way to a Bucklescript FAQ that mentions to state of unit testing: to paraphrase, we're working on Jest bindings and OUnit is a good OCaml testing tool. But its docs are missing or broken.

So I checked out the work-in-progress Jest bindings.

Install bs-jest

bs-jest seems to be the most Reason/JavaScript-oriented testing tool currently available.

In its current form, it's listed as "very very" experimental (which is apparently less experimental than before). It's not 100% complete, but basic APIs seem to be there. Lacking a better option, cowabunga!

In your project, simply:

npm install bs-jest --save-dev

And update your bs-config.json

{
  "bs-dev-dependencies": [
    "bs-jest"
  ]
}

Writing A Test

You can then author your test in Reason:

open Jest;

open MyModule;

let () =
  describe "#myFunction"
    ExpectJs.(fun () => {
      test "some behavior" (fun () => 
        expect(MyModule.doSomething "withArgs") |> toEqual "expectedOutput"
      );
      (* more tests here... *)
    })

Not too bad. Nice and to the point. The Jest module is making the Jest functions you know and love available.

Obviously, the test stub here is relying on the existence of MyModule.doSomething function existing in source code in the ./src directory.

Test Directory

The bs-jest docs tell you to put all your tests under a ./__tests__ directory in your project. This is so that when Bucklescript compiles the tests, they'll be put inside of the ./lib/__tests__ directory of your project, ready to be picked up by the jest runner. This is great.

Also remember to adjust your bsconfig.json to look for these test files, or they won't be found:

{
  "sources": [
    "src",
    "__tests__"
  ]
}

Build your Reason

To build your code, compiling your source and your tests from Reason to JavaScript, you're going to need some Bucklescript goodness:

npm install -g bs-platform

And then in your project root, build via:

bsb -make-world

Now in the ./lib directory you have your source and your tests. Something like this:

lib
├── bs
│   ├── __tests__
│   │   ├── my_module_test.cmi
│   │   ├── my_module_test.cmj
│   │   ├── my_module_test.cmt
│   │   ├── my_module_test.mlast
│   │   └── my_module_test.mlast.d
│   ├── build.ninja
│   └── src
│       ├── my_module.cmi
│       ├── my_module.cmj
│       ├── my_module.cmt
│       ├── my_module.mlast
│       └── my_module.mlast.d
└── js
    ├── __tests__
    │   └── my_module_test.js
    └── src
        └── my_module.js

Lots and lots of files.

Run Tests With Jest

Now you have generated JavaScript. Feed it to Jest like you usually would by running:

jest

To wrap all this up in an npm-script, you might adjust your package.json to include:

{
  "scripts": {
    "build": "bsb -make-world",
    "test": "npm run build && jest"
  }
}

Now to build and run all your Reason unit tests:

npm test

You'll get some output like:

 PASS  lib/js/__tests__/my_module_test.js
  #myFunction
    ✓ some behavior (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.582s, estimated 1s
Ran all test suites.

Yippee! What else do you do to make your Reason unit tests rock?

If you're ready to use this setup, try TDDing a ReasonML function.