Munit Helpers is a suite of Meteor package-testing tools, integrated with the excellent Munit unit testing framework.
Munit Helpers includes:
- Easy stubbing of authentication and collections.
- Easy rendering and testing of Blaze templates.
- Utilities for testing Meteor methods, publications and allow/deny rules.
- Clean, documented APIs wrapping the testing utilities in the Meteor codebase itself
- A few other useful tools for testing Meteor apps and packages.
Munit Helpers is maintained by Tulip. We're an MIT startup located in Boston, helping enterprises manage, understand, and improve their manufacturing operations. We bring our customers modern web-native user experiences to the challenging world of manufacturing, currently dominated by ancient enterprise IT technology. We work on Meteor web apps, embedded software, computer vision, and anything else we can use to introduce digital transformation to the world of manufacturing. If these sound like interesting problems to you, we should talk.
To use Munit Helpers to test a package, just add api.use(["tulip:munit-helpers"])
in your Package.onTest
block.
The Munit Helpers implies the Munit package -- so by adding Munit Helpers, you'll automatically have access to the APIs the Munit provides:
- Munit: Friendly BDD and TDD interfaces to Meteor's built-in tinytest.
- Sinon: Spies stubs and mocks. In general, don't use the sinon API directly; use the Meteor-Sinon API instead, as it tracks what stubs you've made for easy catch-all clean-up.
- Chai: an assertion library.
- Sinon-Chai: Sinon assertions for chai.
Munit Helpers additionally provides the excellent Chai jQuery library, which provides a number of chai assertions that you can make on jQuery objects.
Sets MunitHelpers configuration options. Valid options:
authorizationErrors
: Array of errors that should be considered authorization errors when testing ACL. SeeMunitHelpers.ACL
for details. Defaults to[403]
.
Passing no arguments (instead of a configuration object) will reset the configuration to the default configuration.
Restores all stubs created by any of the methods in MunitHelpers
, plus stubs and spies created by stubs.create()
and spies.create
in Meteor-Sinon. This function can be passed in directly as the tearDown
or suiteTearDown
property of a test suite passed to Munit.run
:
Sets object[property]
to value
and returns a function that restores the stub. Calling this function when the stub has already been restored will cause an error. The stubs can also be restored by calling MunitHelpers.restoreAll()
.
This method is "nestable" -- if the property is already stubbed, that stub will be replaced with this one. When the new stub is restored, the old one will be put back in place, unless it's restored with restoreAll
, which restores all stubs, even nested ones. Trying to restore the old stub before the new one is restored will cause an error.
Restores the stub of property
on object
if it exists. Does nothing if that property is not stubbed.
Returns whether the given property is stubbed.
Restores all changes made by calls to MunitHelpers.StubProperties.stub
Note that many of the other methods of Munit Helpers use MunitHelpers.StubProperties
internally, so this will restore those stubs as well.
Override the Javascript Date object to be a stubbed date at a fixed time. Call this again to change the time. Returns a function that restores the stub. The stub can also be restored by calling MunitHelpers.restoreAll()
.
This method is an alternative to using sinon's fake timers when you don't want to affect setTimeout
and setInterval
.
MunitHelpers.Collections.stub(collection:Collection, dontImportExisting:Optional Boolean) -> Function
Stubs all the methods of the given collection so they work with an in-memory minimongo collection instead of the real database. Imports all records from the real collection into the minimongo collection unless the second argument is set to true. Returns a function that undoes the stubbing. The stubs can also be restored by calling MunitHelpers.restoreAll()
.
This function is idempotent; if you attempt to stub a collection that's already stubbed, this function will do nothing and return a function that does nothing.
Returns whether the given collection is stubbed.
Restores the given collection. Throws an error if the collection is not stubbed. Equivalent to calling the function returned by MunitHelpers.Collections.stub
.
Restores all collections stubbed by MunitHelpers.Collections.stub
.
Runs the given method with the given arguments. Stubs a log-in of the given user if provided. Returns the return value of the method, or throws an error if the method throws an error.
On the client, this uses the client-side stub of the method. It does not actually call to the server. On the server, this uses the server-side version of the method. In both cases, it runs syncronously, returning a value instead of taking a callback.
If a user is passed, this method will stub Meteor.users
if it's not already stubbed, and un-stub it after the method completes. This means that if you're testing a method that modifies Meteor.users
, you should stub Meteor.users
before using MunitHelpers.Methods.apply
to avoid your method's modifications begin made to the stubbed collection and cleared at the end of the method. If the collection is already stubbed and you pass a user object to MunitHelpers.Methods.apply
, the user will be inserted at the start of the method and removed at the end, but Meteor.users
won't be re-stubbed, so your changes will persist.
WARNING: On the server, this internally uses MunitHelpers.Connection.create
, which means it won't work with sinon's fake timers. It does work with MunitHelpers.StubDate.stub
.
Deep-diffs actual
and expected
. Returns undefined
if there are no
differences. Uses deep-diff
internally, with modifications to also provide human-readable difference
descriptions and support custom matching functions.
If there are differences, returns an array of differences. See the deep-diff documentation for details.
As an extension to the upstream library, each item of the array additionally
has a desc
property with a human-readable description of the difference.
As an additional extension, the expected object may contain functions. A
field in actual
is matched against a function in expected
by calling that
function with the actual
field as an argument, and checking whether the
function returns a truthy value. As an example, we could use DeepMatch
to
check whether a particular field is a number greater than 10 with:
MunitHelpers.DeepMatch.expectEqual(actualObject, {
someField: function(value) {
return _.isNumber(value) && (value > 10);
}
});
and then { someField: 15 }
would return no differences, but { someField: 5 }
would return the difference description:
[
{
kind: "M",
path: [ "someField" ],
lhs: 5,
desc: "Function matcher didn't match at someField"
}
]
This function is also exposed as the deepMatch
helper in chai, so you can
use expect(actualObject).to.deepMatch(expectedObject)
in your tests. This
is a drop-in replacement for expect(actualObject).to.deep.equal(expecteObject)
that gives much better error messages.
Stubs Meteor.users
using MunitHelpers.Colletions.stub
and inserts the given user record. If the given record does not have an _id
field, one will be added. Returns a function that removes the given user from the stubbed Meteor.users
collection, and then un-stubs Meteor.users
if it was not stubbed before stubUser
was called.
Stubs Meteor.user
and Meteor.userId
to return the given user (or null). If the given user isn't null and does not have an _id
field, an _id
field will be added. If the user record is a string (the ID of a user), it will stub the login without adding the user to the Meteor.users collection. Returns a function that can be called to restore the stubs. The stubs can also be restored by calling MunitHelpers.restoreAll()
.
Given a template (e.g. Template.fooBar
) and a data context, renders the template and returns a jQuery-like function that only searches within the rendered template.
WARNING: the function renders the template but does not attach the result to the DOM. This means that if your template uses the global $
function to select elements within the template, it won't find anything. Instead, your template should use the template-scoped jQuery function. This function can be accessed as this.$
in created
, rendered
, and destroyed
callbacks; as Template.instance().$
in helpers; and as template.$
in event maps, where template
is the second argument to the event handler. See the Meteor Docs for more details. If you really need to use the global jQuery function, your test can use the return value of render
to find the top-level element in your template, and then call appendTo
to append it to the DOM, like this:
var $ = MunitHelpers.Template.render(Template.myTemplate, {some: "data"});
$(".top-level-el").appendTo("body");
MunitHelpers.Templates.renderLayout(layoutTemplate:Template, contentTemplate:Template, data:Object) -> Function
Given an Iron Layout template, renders it with the content template as the main yield block, using the given data context. Returns a jQuery-like function that only searches within the rendered template. Munit Helpers has a weak dependency on iron:layout
, so your app or package must require iron:layout
for you to use this function.
Given the name of a template and static HTML content for it, returns the new template. Useful for creating content templates to pass to MunitHelpers.Templates.renderLayout
.
Given the jQuery-like object returned by MunitHelpers.Templates.render
or MunitHelpers.Templates.renderLayout
, destroys the template to stop reactive updates.
Restores all templates created by MunitHelpers.Templates.render
or MunitHelpers.Templates.renderLayout
.
Utilities for testing whether DB operations would be permitted by the package's allow/deny rules. Each of these methods takes the collection to check, the arguments that would be passed to the call, and an optional user. If the user is provided, the allow/deny rules will be run with that user is logged in. These methods return true if the insert was permitted, false if it was forbidden, and raise an error if an allow or deny rule raises an error.
By default, if the ACL rule raises a Meteor.Error
with the error 403
, Munit Helpers considers that a forbidden response, and returns false
instead of re-raising the error. If your code base uses other errors to indicate a forbidden response, you can configure additional errors types to be treated as forbidden errors. For example, calling MunitHelpers.configure({ authorizationErrors: [ 403, "AuthorizationError" ] })
will cause these ACL checks to return false if an ACL rule does a throw new Meteor.Error("AuthorizationError", "SomeMessage")
.
Note that the insert/update/remove is not actually performed -- we stub out the method that would actually do that. If you want to have the collection contain documents (which you probably do when testing update/remove rules), you can stub the collection with MunitHelpers.Collections.stub
before calling these methods.
Creates a connection to this server. Returns an object with three properties:
clientConn
, the client end of the connection (as would be returned byDDP.connect
)serverConn
, the server end of the connection (as would be passed toMeteor.onConnection
).restore
, which closes down the connection.
WARNING: This method does not work with sinon's fake timers. It does work with MunitHelpers.StubDate.stub
.
Stubs a log-in of the given user and marks that user as logged in to the given connection (pass the object returned by MunitHelpers.Connection.create
). Returns a function that restores the stubbing. The stubs can also be restored by calling MunitHelpers.restoreAll()
.
Closes down all connections created by MunitHelpers.Connection.create
. This is also done by MunitHelpers.restoreAll()
.
Runs the given publish function with the given args, with the given user logged in. Returns an object with three properties:
collection(name:String) -> Mongo.Collection
: Returns a Minimongo collection that's populated with the results of the publish function. This is similar to callingnew Mongo.Collection(name)
and thenMeteor.subscribe(pubName)
, but the collection is connected only to the specific subscription you're testing.stop()
stops the subscriptions, equivalent to callingstop
on the object returned byMeteor.subscribe
.ready()
returns whether the current subscription is ready (has returned a cursor or calledthis.ready
).
WARNING: This internally uses MunitHelpers.Connection.create
, which means it won't work with sinon's fake timers. It does work with MunitHelpers.StubDate.stub
.
While we avoid it wherever possible, Munit Helpers does have to use a few undocumented Meteor features. We document them here because they are the most brittle parts of the package. In addition, the unit tests for this package have been designed to break if any of these undocumented features change or are removed.
-
In
MunitHelpers.ACL
, we use the internal collection methods_validatedInsert
,_validatedRemove
, and_validatedUpdate
so that allow/deny rules will be run even though we're on the server. -
In
MunitHelpers.Connection.create
, we usemakeTestConnection
from the test-helpers package, which is an undocumented core Meteor package. -
In
MunitHelpers.Connection.stubLogin
, we directly modifyMeteor.server.sessions
, which is an internal data structure Meteor uses to keep track of authentications.
If you've found a bug or have a feature request, file an issue on Github.
You also join the mailing list if you're interested in getting involved with development.
How to submit changes:
- Fork this repository.
- Make your changes, including adding or changing appropriate tests.
- Verify all tests pass with
meteor test-packages ./
and that there are not jshint erros withjshint .
- Email us as [email protected] to sign a CLA.
- Submit a pull request.
Pull requests that change or add features and don't have associated unit tests won't be accepted. If you're planning on implementing a large feature, you should email the mailing list first so we can make sure we're on the same page.
Pull requests must also follow the conventions below and pass jshint using the .jshintrc
in this repo.
- Stroustrup Indentation Style
- Four spaces, no tabs
- Trailing newline in all files
- Everything in our jshintrc
Munit Helpers is licensed under the Apache Public License.