Awesome
Must.js
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 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.
Tour
-
Assert with a beautiful and fluent chain that saves you from wrapping objects manually and reads nicely, too:
require("must/register") obj.must.be.true()
-
Supports the expect flavor of wrapping as well:
var demand = require("must") demand(obj).be.string()
-
Many expressive matchers out of the box, including:
[].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 propertyempty
. See below why asserting on property access is dangerous in other assertion libraries. -
Has an intelligent and type-safe recursive
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.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.
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 by using the word MUST because your tests assert things, they don't list wishes or prayers, right? Exactly!
Foo.must.equal(42)
, notfoo.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 and 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.
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 by others available, too.
Installing
Note: Must.js will follow the semantic versioning 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 to have it run there till then.
Using
To use the fluent chain, just require Must.js's "register" file and it'll make itself available everywhere:
require("must/register")
Then just access the must
property on any object and call matchers on it.
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:
var expect = require("must")
var demand = require("must")
And call it with the object you wish to assert:
expect(answer).to.equal(42)
demand(null).be.null()
For a list of all matchers, please see the Must.js API Documentation.
Negative asserting or matching the opposite
To assert the opposite, just add not
between the chain:
true.must.not.be.false()
[].must.not.be.empty()
Use it multiple times to create lots of fun puzzles! :-)
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:
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
matcher:
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, that file is test/mocha.opts
:
--require must/register
Full example
Inside a test runner or framework things would look something like this:
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.
Must
- .prototype.a(class)
- .prototype.above(expected)
- .prototype.after(expected)
- .prototype.an(class)
- .prototype.array()
- .prototype.at
- .prototype.be(expected)
- .prototype.before(expected)
- .prototype.below(expected)
- .prototype.between(begin, end)
- .prototype.boolean()
- .prototype.contain(expected)
- .prototype.date()
- .prototype.empty()
- .prototype.endWith(expected)
- .prototype.enumerable(property)
- .prototype.enumerableProperty(property)
- .prototype.eql(expected)
- .prototype.equal(expected)
- .prototype.error([constructor], [expected])
- .prototype.eventually
- .prototype.exist()
- .prototype.false()
- .prototype.falsy()
- .prototype.frozen()
- .prototype.function()
- .prototype.gt(expected)
- .prototype.gte(expected)
- .prototype.have
- .prototype.include(expected)
- .prototype.instanceOf(class)
- .prototype.instanceof(class)
- .prototype.is(expected)
- .prototype.keys(keys)
- .prototype.least(expected)
- .prototype.length(expected)
- .prototype.lt(expected)
- .prototype.lte(expected)
- .prototype.match(regexp)
- .prototype.most(expected)
- .prototype.must
- .prototype.nan()
- .prototype.nonenumerable(property)
- .prototype.nonenumerableProperty(property)
- .prototype.not
- .prototype.null()
- .prototype.number()
- .prototype.object()
- .prototype.own(property, [value])
- .prototype.ownKeys(keys)
- .prototype.ownProperties(properties)
- .prototype.ownProperty(property, [value])
- .prototype.permutationOf(expected)
- .prototype.properties(properties)
- .prototype.property(property, [value])
- .prototype.regexp()
- .prototype.reject
- .prototype.resolve
- .prototype.startWith(expected)
- .prototype.string()
- .prototype.symbol()
- .prototype.the
- .prototype.then
- .prototype.throw([constructor], [expected])
- .prototype.to
- .prototype.true()
- .prototype.truthy()
- .prototype.undefined()
- .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 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 todemand(foo).not.to.exist()
. - Must.js lacks
with.lengthOf
because its matchers are all independent.
Convert toobj.must.have.length(5)
- Must.js lacks the
ok
matcher because unambiguous names are better.
Convert totruthy
. - 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 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 toObject.keys(obj).must.include("foo")
. - Must.js lacks the
deep
flag for theequal
matcher becauseeql
already compares recursively and in a type-safe way.
Convert toobj.must.eql({some: {deep: "object"}})
. - Must.js lacks the
deep
flag for theproperty
matcher because it prefers regular property access.
Convert toobj.some.nested.property.must.equal(42)
. - Must.js lacks the
ok
matcher because unambiguous names are better.
Convert totruthy
. - Must.js lacks the
respondTo
matcher because unambiguous names are better.
Convert toMyClass.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 and 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:
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()
.
Plugins
- must-sinon (Repository) — Sinon assertions.
- must-targaryen (Repository) — Firebase Targaryen assertions.
- must-jsx (Repository] — 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 typed this and the code.
Monday Calendar supported the engineering work.
BrowserStack supports with cross-browser testing.
If you find Must.js needs improving, please don't hesitate to type to me now at andri@dot.ee or create an issue online.