A pure, functional† unit testing tool with a dependency-free test suite API.
npm install washington --dev
Washington provides a CLI, so you can install it globally to get the washington
command:
npm install -g washington
// tests.js
const add = (x, y) => x + y
module.exports = [
{
description: 'returns 2 when adding 1 and 1',
test: check => check(add(1, 1)),
shouldEqual: 2
},
{
description: 'returns 4 when adding 2 and 2',
test: check => check(add(2, 2)),
shouldEqual: 4
}
]
> washington test.js
The washington
command exits the process with an exit code equal to the number of failing examples. This takes advantage of the fact that any non-zero exit code means that the command failed.
const washington = require('washington')
const add = (x, y) => x + y
washington([
{
description: 'returns 2 when adding 1 and 1',
test: check => check(add(1, 1)),
shouldEqual: 2
}
])
By default the washington
function also exits the process with an exit code equal to the number of failing examples.
const addLater = (x, y, callback) => {
setTimeout(() => callback(x + y))
}
module.exports = [
{
description: 'will eventually add 1 and 1 and pass 2 to the callback',
test: check => addLater(1, 1, result => check(result)),
shouldEqual: 2
}
]
module.exports = [
{
description: 'has the expected data structure',
test: check => check({ a: [1, '2', false] }),
shouldEqual: { a: [1, '2', false] }
}
]
This is because assertions are done with assert.deepEqual
.
const add = (x, y) => x + y
module.exports = [
{
description: 'returns 2 synchronously when adding 1 and 1',
test: () => add(1, 1),
shouldEqual: 2
}
]
module.exports = [
{
description: 'is so test, many unit'
},
{
description: 'buys milk'
}
]
import washington from 'washington'
import washingtonFormatterBrowser from 'washington.formatter.browser'
const add = (x, y) => x + y
const suiteTask = washington(
[
{
description: 'returns 3 when adding 1 and 2',
test: check => check(add(1, 2)),
shouldEqual: 3
}
],
{ safe: true }
)
suiteTask
.chain(washingtonFormatterBrowser(console.log))
.run()
There is no Karma adapter yet. Make an issue or pull request if you want one.
If you get bored of writing the arrays of examples by hand, Washington comes bundled with a neat DSL to write them in a more compact way.
example(description, test, shouldEqual)
: builds and returns the example object.suite(prefix, ...examples)
: prepends the prefix to the description of every example that you pass it to it, and returns the array with the modified examples.
import { example, suite } from 'washington'
export default suite(
'addition',
example(
'1 and 2 make 3',
() => 1 + 2,
3
),
example(
'2 and 4 make 6',
() => 2 + 4,
6
),
example('pending example')
)
You might wonder, "Why don’t you open the README with this example? It looks like a cleaner API than writing the objects manually, and more similar to other tools".
Well, the thing is that the DSL functions of most JS testing libraries behave in a very different way than Washington, doing things behind the scenes, and by showing examples that looked like those of other libraries, it might seem that Washington works the same way. But half of the point of Washington is that the test suite is a simple array of plain objects, so that you can manipulate it in any way you want. Showing that instead helps hammer the point home.
The DSL functions are extremely lean. There is no magic at all in them. Take a look:
// packages/washington.dsl/index.js
const suite = (name, ...suite) =>
suite.map(x => ({...x, description: `${name}: ${x.description}`}))
const example = (description, test, shouldEqual) =>
({ description, test, shouldEqual })
Output TAP instead of the default colors:
const washington = require('washington')
const washingtonFormatterTAP = require('washington.formatter.tap')
const add = (x, y) => x + y
const suiteTask = washington(
[
{
description: 'returns 2 when adding 1 and 1',
test: check => check(add(1, 1)),
shouldEqual: 2
},
{
description: 'returns 4 when adding 2 and 2',
test: check => check(add(2, 2)),
shouldEqual: 4
}
],
{safe: true}
)
suiteTask
.chain(washingtonFormatterTAP(console.log))
.run()
Maybe you want to use this tool for something else other than simple unit testing? Displaying results interactively in a REPL, in the browser, or sending them over a network…
I always thought that a library like this might be useful for exercises such as Ruby Koans.
Check the suite result object structure for details on the object structure that Washington puts inside the Task.
const washington = require('washington')
const add = (x, y) => x + y
const suiteTask = washington(
[
{
description: 'returns 2 when adding 1 and 1',
test: check => check(add(1, 1)),
shouldEqual: 2
},
{
description: 'returns 4 when adding 2 and 2',
test: check => check(add(2, 2)),
shouldEqual: 4
},
{
description: 'gets chocolate as well'
}
],
{safe: true} // This prevents the default output formatter from running
)
suiteTask
.map(result => {
console.log('object structure result', result)
})
.run()
map
is used in this example because most users need not be familiar withchain
and Folktale Tasks. If you are, I suggest that you use that instead.
The washington
function takes two arguments. It returns a Task
when running safe
. When the safe
option false
or not set, it will quit the process before returning.
const suiteTask = washington(
testSuite, // explained in the previous examples
{
// When set to be `safe`, washington will not actually run anything.
// Instead, it will return a Folktale Task that you can consume
// to add your own output formatter or manipulate the result in any
// other way
safe: true
}
)
suiteTask
.map(suiteResult => {
console.log(suiteResult)
return suiteResult
})
suiteTask
.chain(suiteResult => Task.of(suiteResult))
The suiteTask
is a Folktale Task. In a nutshell, that means that you can map
or chain
over it to get access to the results. These operations are defined in the Fantasy Land specification.
If you are not familiar with this, think of map
and chain
as the then
of a Promise. In the "Why do you say this is functional" section there are links to great tutorials if you want to learn more.
The suiteResult
structure looks like this:
[
{
description: 'some examples that is successful',
test: () => 1 + 1,
shouldEqual: 2,
result: {
type: 'success'
}
},
{
description: 'something pending',
result: {
type: 'pending'
}
},
{
description: 'an error',
test: check => check(1 + 1),
shouldEqual: 3,
result: {
type: 'failure',
message: 'the error message',
stack: [
'a list of',
'lines',
'from the stack trace'
],
original: new Error // Original error object
}
}
]
Washington is a library and a CLI, not a testing framework. This means that it does not enforce any file structure for testing and does not do any discovery of files in your project. So, how do you set it up to use it in yours?
Say you have a project with the structure:
example-project/
src/
addition.js
addition.test.js
package.json
…and addition.js
looks like:
// src/addition.js
function addition (x, y) {
return x + y
}
module.exports = addition
Then you can write the tests in the addition.test.js
as follows:
// src/addition.test.js
const addition = require('./addition')
module.exports = [
{
description: 'returns 2 when adding 1 and 1',
test: check => check(addition(1, 1)),
expectedValue: 2
},
{
description: 'returns 4 when adding 1 and 3',
test: check => check(addition(1, 3)),
expectedValue: 4
}
]
…and in the package.json
set the test script to use Washington with that file as input:
…
"scripts": {
"test": "washington src/addition.test.js"
}
…
The output of this will be: // TODO image
OK, so what if I have another file in my project, that I want to test? Let’s add multiplication:
example-project/
src/
addition.js
addition.test.js
+ multiplication.js
+ multiplication.test.js
package.json
// src/multiplication.js
function multiplication (x, y) {
return x * y
}
module.exports = multiplication
// src/multiplication.test.js
const multiplication = require('./multiplication')
module.exports = [
{
description: 'returns 1 when multiplying 1 by 1',
test: check => check(multiplication(1, 1)),
shouldEqual: 1
},
{
description: 'returns 6 when multiplying 2 by 3',
test: check => check(multiplication(2, 3)),
shouldEqual: 6
}
]
Well, the exported values of these two test files (addition.test.js
and multiplication.test.js
) are just arrays. There is a very simple solution here. Let’s create and src/index.test.js
:
example-project/
src/
addition.js
addition.test.js
multiplication.js
multiplication.test.js
+ index.test.js
package.json
// src/index.test.js
const additionTest = require('./addition.test')
const multiplicationTest = require('./multiplication.test')
module.exports = additionTest.concat(multiplicationTest)
…and in the package.json
:
…
"scripts": {
"test": "washington src/index.test"
}
…
Remember, there is nothing fancy going on here, your test are just data, so you can feel free to manipulate them that way. You can make a script that grabs all .test.js
file by a glob pattern and concat them all together if you feel so inclined. Washington’s only concern is that you pass in an array of example objects.
Washington is an opinionated tool regarding namespaces. Washington thinks you don’t need them.
There are two reasons:
- You can namespace you example descriptions.
'Addition: 1 and 1 give 2'
. - If you need a deeply nested structure of tests, there’s probably room for simplification in the app/library.
But sure some of you disagree! Well, if you for some reason you really like Washington’s approach and would like to use it, but you also really want to get a nested structure of tests, remember: examples are just plain JavaScript objects. You are more than welcome to add a namespace
or breadcrumbs
property to them and use it in a custom formatter to organize the output of the test suite.
† As much as it can be in JavaScript
Washington is built on principles inspired or directly taken from the Fantasy Land community. Furthermore, the test suite is just a regular array of simple objects, there is no hidden magic or state anywhere. You can easily write your own lib that consumes the Washington example format. I believe this makes Washington fairly future proof—time will tell.
Washington is also friendly to a functional programming approach by providing a nice out-of-the-box experience for testing pure functions. Because the test cases are just plain objects, it’s easy to imagine automating test scenario generation. Washington could be easily combined with jsverify for this purpose.
Shoutout to [DrBoolean](https:// egghead.io/instructors/brian-lonsdorf) who should take credit of most of my education in functional JavaScript
- George Washington gave us a good example
- We all know that he can’t lie
Tests for Washington are written in Washington. I really believe in dogfooding and ain’t afraid of self reference.
This library is transpiler free. I ❤️ Babel but it’s not necessary for this.
The project is a multi-package repo managed with Lerna. To get started, clone and then run:
> npm install
> npm run bootstrap
This installs lerna locally and then run lerna bootstrap
that will set up all necessary symlinks between the packages.
Copyright 2014 Fernando Vía Canel
BSD 2 Clause license