must
Version:
Testing and assertion library with friendly BDD syntax — awesome.must.be.true(). Many expressive matchers and is test runner and framework agnostic. Follows RFC 2119 with its use of MUST. Good and well tested stuff.
428 lines (353 loc) • 20.5 kB
Markdown
Must.js
=======
[![NPM version][npm-badge]](https://www.npmjs.com/package/must)
[![Build status][travis-badge]](https://travis-ci.org/moll/js-must)
Must.js is a testing and assertion library for JavaScript and Node.js with
a friendly **BDD** syntax (`awesome.must.be.true()`). It ships with **many
expressive matchers** and is **test runner and framework agnostic**. Follows
[RFC 2119][rfc2119] with its use of **MUST**. Good and well testsed stuff.
For those new to testing JavaScript on Node.js, you'll also need a test
framework (also called a test-runner or a harness) to run your tests. One such
tool is [Mocha][mocha].
[npm-badge]: https://img.shields.io/npm/v/must.svg
[travis-badge]: https://travis-ci.org/moll/js-must.png?branch=master
[rfc2119]: https://www.ietf.org/rfc/rfc2119.txt
[mocha]: https://mochajs.org
### Tour
- Assert with a **beautiful and fluent chain** that saves you from wrapping
objects manually and reads nicely, too:
```javascript
require("must/register")
obj.must.be.true()
```
- Supports the **expect flavor** of wrapping as well:
```javascript
var demand = require("must")
demand(obj).be.string()
```
- **Many expressive matchers** out of the box, including:
```javascript
[].must.be.empty()
obj.must.have.nonenumerable("foo")
(42).must.be.above(13)
```
- **Simple**, because **matchers always behave the same way** and don't depend
on any "special flags" in the chain. They are also **not interdependent** the
way `foo.should.have.property(x).with.lengthOf(5)` would be.
- **Reasonable**, because it asserts only when you call the matcher
`[].must.be.empty()` and not when you merely get the property `empty`. See
below why [asserting on property access](#asserting-on-property-access) is
**dangerous** in other assertion libraries.
- Has an **intelligent and type-safe** recursive [`eql`][Must.prototype.eql]
matcher that compares arrays and objects by content and supports value
objects. It's fully type-safe, so instances of *different classes* aren't
*eql*, even if their properties are. It also supports **circular and
self-referential** objects.
```javascript
primesBelowTen.must.eql([2, 3, 5, 7])
model.attributes.must.eql({title: "New", createdAt: new Date(2000, 1, 1)})
```
- Built-in support for **asserting on promises** with stack traces leading back
to _your_ assertion, not to the library's internals.
```javascript
Promise.resolve(42).must.then.equal(42)
Promise.resolve([1, 2, 3]).must.eventually.not.include(42)
Promise.reject(new Error("Problemo")).must.reject.with.error(/problem/i)
```
- **Human readable error messages** let you know if an object wasn't what you
expected. You can also customize or prepend to the autogenerated error
message for further clarification.
- Honors [RFC 2119][rfc2119] by using the word **MUST** because your
tests assert things, they don't list wishes or prayers, right? Exactly!
`Foo.must.equal(42)`, not `foo.pretty.please.equal(42)`.
- Works with any test runner and framework.
- Avoids type coercions and mismatches.
- Well tested — over 700 cases in over 2500 lines of tests. That makes a test to
code ratio of 5:1.
### Using Should.js or Chai.js? Switch for safety!
Among other things, one reason why [Should.js][should.js] and [Chai.js][chai.js]
inspired me to write Must.js is that they have a **fundamental design mistake**
that makes them both **surprising in a bad way** and **dangerous to use**. [Read
more below](#asserting-on-property-access).
### Extensible
Must.js features a very simple implementation and one you can extend yourself. In Must.js, every matcher is a function on `Must.prototype` that calls `Must.prototype.assert`. For now, please see the source of Must for examples.
There are [plugins for Must.js](#plugins) by others available, too.
Installing
----------
**Note**: Must.js will follow the [semantic versioning](http://semver.org/)
starting from v1.0.0.
### Installing on Node.js
```
npm install must
```
### Installing for the browser
Must.js doesn't yet have a build ready for the browser, but you might be able
to use [Browserify][browserify] to have it run there till then.
[browserify]: https://github.com/substack/node-browserify
Using
-----
To use the **fluent chain**, just require Must.js's "register" file and it'll
make itself available everywhere:
```javascript
require("must/register")
```
Then just access the `must` property on any object and call matchers on it.
```javascript
answer.must.equal(42)
new Date().must.be.an.instanceof(Date)
```
If you wish to use the **expect flavor**, assign Must to any name of your
choice, e.g:
```javascript
var expect = require("must")
var demand = require("must")
```
And call it with the object you wish to assert:
```javascript
expect(answer).to.equal(42)
demand(null).be.null()
```
For a list of all matchers, please see the [Must.js API Documentation][api].
### Negative asserting or matching the opposite
To assert the opposite, just add `not` between the chain:
```javascript
true.must.not.be.false()
[].must.not.be.empty()
```
Use it multiple times to create lots of fun puzzles! :-)
```javascript
true.must.not.not.be.true()
```
### Asserting on null and undefined values
In almost all cases you can freely call methods on any object in JavaScript.
Except for `null` and `undefined`.
Most of the time this won't be a problem, because if you're asserting that
`something.must.be.true()` and `something` ends up `null`, the test will still
fail. If, however, you do need to assert its nullness, aliasing Must to `expect`
or `demand` and wrapping it manually works well:
```javascript
var demand = require("must")
demand(something).be.null()
demand(undefined).be.undefined()
```
If you've got an object on which a `null` or an `undefined` property must
_exist_ in addition to having a nully value, use the
[`property`][Must.prototype.property] matcher:
```javascript
var obj = {id: null, name: undefined}
obj.must.have.property("id", null)
obj.must.have.property("name", undefined)
```
### Autoloading
If your test runner supports an options file, you might want to require Must
there so you wouldn't have to remember to `require` in each test file.
For [Mocha][mocha], that file is `test/mocha.opts`:
```
--require must/register
```
### Full example
Inside a test runner or framework things would look something like this:
```javascript
require("must/register")
var MySong = require("../my_song")
describe("MySong", function() {
it("must be creatable", function() {
new MySong().must.be.an.instanceof(MySong)
})
it("must have cowbell", function() {
new MySong().cowbell.must.be.true()
})
it("must not have pop", function() {
new MySong().must.not.have.property("pop")
})
})
```
API
---
For extended documentation on all functions, please see the
[Must.js API Documentation][api].
[api]: https://github.com/moll/js-must/blob/master/doc/API.md
### [Must](https://github.com/moll/js-must/blob/master/doc/API.md#Must)
- [.prototype.a](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.a)(class)
- [.prototype.above](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.above)(expected)
- [.prototype.after](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.after)(expected)
- [.prototype.an](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.an)(class)
- [.prototype.array](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.array)()
- [.prototype.at](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.at)
- [.prototype.be](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.be)(expected)
- [.prototype.before](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.before)(expected)
- [.prototype.below](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.below)(expected)
- [.prototype.between](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.between)(begin, end)
- [.prototype.boolean](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.boolean)()
- [.prototype.contain](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.contain)(expected)
- [.prototype.date](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.date)()
- [.prototype.empty](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.empty)()
- [.prototype.endWith](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.endWith)(expected)
- [.prototype.enumerable](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.enumerable)(property)
- [.prototype.enumerableProperty](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.enumerableProperty)(property)
- [.prototype.eql](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.eql)(expected)
- [.prototype.equal](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.equal)(expected)
- [.prototype.error](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.error)([constructor], [expected])
- [.prototype.eventually](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.eventually)
- [.prototype.exist](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.exist)()
- [.prototype.false](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.false)()
- [.prototype.falsy](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.falsy)()
- [.prototype.frozen](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.frozen)()
- [.prototype.function](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.function)()
- [.prototype.gt](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.gt)(expected)
- [.prototype.gte](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.gte)(expected)
- [.prototype.have](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.have)
- [.prototype.include](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.include)(expected)
- [.prototype.instanceOf](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.instanceOf)(class)
- [.prototype.instanceof](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.instanceof)(class)
- [.prototype.is](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.is)(expected)
- [.prototype.keys](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.keys)(keys)
- [.prototype.least](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.least)(expected)
- [.prototype.length](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.length)(expected)
- [.prototype.lt](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.lt)(expected)
- [.prototype.lte](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.lte)(expected)
- [.prototype.match](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.match)(regexp)
- [.prototype.most](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.most)(expected)
- [.prototype.must](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.must)
- [.prototype.nan](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.nan)()
- [.prototype.nonenumerable](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.nonenumerable)(property)
- [.prototype.nonenumerableProperty](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.nonenumerableProperty)(property)
- [.prototype.not](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.not)
- [.prototype.null](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.null)()
- [.prototype.number](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.number)()
- [.prototype.object](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.object)()
- [.prototype.own](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.own)(property, [value])
- [.prototype.ownKeys](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.ownKeys)(keys)
- [.prototype.ownProperties](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.ownProperties)(properties)
- [.prototype.ownProperty](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.ownProperty)(property, [value])
- [.prototype.permutationOf](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.permutationOf)(expected)
- [.prototype.properties](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.properties)(properties)
- [.prototype.property](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.property)(property, [value])
- [.prototype.regexp](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.regexp)()
- [.prototype.reject](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.reject)
- [.prototype.resolve](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.resolve)
- [.prototype.startWith](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.startWith)(expected)
- [.prototype.string](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.string)()
- [.prototype.symbol](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.symbol)()
- [.prototype.the](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.the)
- [.prototype.then](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.then)
- [.prototype.throw](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.throw)([constructor], [expected])
- [.prototype.to](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.to)
- [.prototype.true](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.true)()
- [.prototype.truthy](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.truthy)()
- [.prototype.undefined](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.undefined)()
- [.prototype.with](https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.with)
Migrating to Must.js
--------------------
You're likely to be already using some testing library and have a set of tests
in them. I'm **honored you picked** Must.js to go forward. Let's **get you up to
speed** on how Must.js differs from others and how to **migrate your old tests**
over.
### From Should.js
Must.js and [Should.js][should.js] are fairly similar when it comes to matchers.
- Just add parentheses after each assertion and you're almost set.
- Must.js does not have static matchers like `should.not.exist(obj.foo)`.
Convert to `demand(foo).not.to.exist()`.
- Must.js lacks `with.lengthOf` because its matchers are all independent.
Convert to `obj.must.have.length(5)`
- Must.js lacks the `ok` matcher because unambiguous names are better.
Convert to `truthy`.
- Must.js does not support custom error descriptions.
Here's a quick `sed` script to convert `obj.should.xxx` style to
`obj.must.xxx()`:
```
sed -i.should -E -f /dev/stdin test/**/*.js <<-end
/\.should\.([[:alpha:].]+)([[:space:]}\);]|$)/s/\.should\.([[:alpha:].]+)/.must.\1()/g
s/\.should\.([[:alpha:].]+)/.must.\1/g
end
```
### From Chai.js
Must.js and [Chai.js][chai.js] are fairly similar when it comes to matchers.
- Just add parentheses after each assertion and you're almost set.
That goes for both the BDD (`obj.should`) and *expect*
(`expect(obj).to`) flavor.
- Must.js lacks the `include` flag because its matchers are all independent.
Convert to `Object.keys(obj).must.include("foo")`.
- Must.js lacks the `deep` flag for the `equal` matcher because
[`eql`][Must.prototype.eql] already compares recursively and in a type-safe
way.
Convert to `obj.must.eql({some: {deep: "object"}})`.
- Must.js lacks the `deep` flag for the `property` matcher because it prefers
regular property access.
Convert to `obj.some.nested.property.must.equal(42)`.
- Must.js lacks the `ok` matcher because unambiguous names are better.
Convert to `truthy`.
- Must.js lacks the `respondTo` matcher because unambiguous names are better.
Convert to `MyClass.prototype.must.be.a.function()`.
Here's a quick `sed` script to convert `obj.should.xxx` style to
`obj.must.xxx()`:
```
sed -i.should -E -f /dev/stdin test/**/*.js <<-end
/\.should\.([[:alpha:].]+)([[:space:]}\);]|$)/s/\.should\.([[:alpha:].]+)/.must.\1()/g
s/\.should\.([[:alpha:].]+)/.must.\1/g
end
```
### Convert test case titles to MUST
If you've used the `should` style before, you most likely have test cases titled
`it("should do good")`.
Migrate those to `it("must do good")` with this `sed` script:
```
sed -i.should -E -e 's/it\("should/it("must/g' test/**/*.js
```
<a name="asserting-on-property-access"></a>
### Beware of libraries that assert on property access
Among other things, one reason why [Should.js][should.js] and
[Chai.js][chai.js] inspired me to write Must.js is that they have
a **fundamental design mistake** that makes them both **surprising in a bad
way** and **dangerous to use**.
It has to do with them asserting on property access, like this:
```javascript
true.should.be.true
[].should.be.empty
```
What initially may seem familiar to Ruby programmers, first of all, is out of
place in JavaScript. Dot-something stands for getting a property's value and
getters, regardless of language, **should not** have **side-effects**.
Especially not **control-flow changing exceptions**!
Secondly, and this is where it's flat out **dangerous asserting on property
access**, is that accessing a non-existent property does **nothing** in
JavaScript. Recall that JavaScript does not have Ruby's `method_missing` or
other hooks to catch such access. So, guess what happens when someone mistypes
or mis-remembers a matcher? Yep, nothin' again. And that's the way it's supposed
to be. But what's good in JavaScript, **not so good** for your now **false
positive test**.
Imagine using a plugin that adds matchers for spies or mocks. Then using it with
`someFn.should.have.been.calledOnce`. Someone accidentally removes the plugin
or thinks `calledQuadrice` sounds good? Well, those assertions will surely
continue passing because they'll now just get `undefined` back.
Must.js **solves both problems** with the **simplest but effective solution**
— requires you to **always call matchers** because they're plain-old functions
— `expect(problem).to.not.exist()`.
[should.js]: https://github.com/visionmedia/should.js
[chai.js]: http://chaijs.com
Plugins
-------
- [must-sinon](https://www.npmjs.com/package/must-sinon) ([Repository](https://github.com/JohnnyEstilles/must-sinon)) — Sinon assertions.
- [must-targaryen](https://www.npmjs.com/package/must-targaryen) ([Repository](https://github.com/jtwebman/must-targaryen)) — Firebase Targaryen assertions.
- [must-jsx](https://www.npmjs.com/package/must-jsx) ([Repository](https://github.com/nwinch/must-jsx)] — React.js JSX assertions.
If you have a module extending Must.js one not listed above, please let me know or create a pull request.
License
-------
Must.js is released under a *Lesser GNU Affero General Public License*, which in
summary means:
- You **can** use this program for **no cost**.
- You **can** use this program for **both personal and commercial reasons**.
- You **do not have to share your own program's code** which uses this program.
- You **have to share modifications** (e.g bug-fixes) you've made to this
program.
For more convoluted language, see the `LICENSE` file.
About
-----
**[Andri Möll](http://themoll.com)** typed this and the code.
[Monday Calendar](https://mondayapp.com) supported the engineering work.
If you find Must.js needs improving, please don't hesitate to type to me now at
[andri@dot.ee][email] or [create an issue online][issues].
[email]: mailto:andri@dot.ee
[issues]: https://github.com/moll/js-must/issues
[Must.prototype.eql]: https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.eql
[Must.prototype.property]: https://github.com/moll/js-must/blob/master/doc/API.md#Must.prototype.property