@bigtest/mocha
Convergent Mocha functions for testing against asynchronous states
Synopsis
import { describe, beforeEach, it } from '@bigtest/mocha';
import { expect } from 'chai';
describe('clicking my button', () => {
beforeEach(() => $button.click());
it('shows a loading indicator', () => {
expect($button.className).to.include('is-loading');
});
it.always('does not navigate away', () => {
expect(app.location).to.equal('/')
}).timeout(200);
})
Convergent Assertions
Typically, when testing asynchronous states (such as rendered content
in an application) your tests need to run at just the right moment so
that they are executed in the correct context. If your tests run too
soon, they will fail; if they run too slow, well then you just have
slow tests.
Converging on a state means asserting against a state until the
assertion passes, in which case you have successfully converged on
that state!
This package uses
@bigtest/convergence
to repeatedly run assertions until they pass, or until the timeout has
expired. Performing tests in this way allows them to pass the moment
the desired state is achieved. This results in very fast tests when
testing asynchronous things.
Read the @bigtest/convergence
docs on
convergences
for more info as to why converging on a desired state is better than
trying to time it properly.
How does it work?
For the most part, you write tests in the exact same way that you're
used to writing tests with Mocha. The only difference is that this
package wraps Mocha's it
in a convergence helper so that any
assertions you write using it
become convergent assertions that
allow you to easily test asynchronous states.
This package also wraps the Mocha hooks before
, after
,
beforeEach
, and afterEach
to support automatically timing and
running returned Convergence
instances from @bigtest/convergence
.
Writing Tests
Because convergent assertions are run repeatedly until they pass, it
is highly recommended that you do not perform any side-effects
in your assertions. This will result in your side-effect being run
multiple, perhaps even hundreds of times.
For this reason, you should keep your side-effect producing code in
hooks and out of your assertions. These "pure assertions" also
help your tests be more readable and explicit.
:no_entry_sign: do not do this:
describe('my button', () => {
it('shows a loading indicator after clicking', () => {
$button.click();
expect($button.className).to.include('is-loading');
});
});
:white_check_mark: do this:
describe('clicking my button', () => {
beforeEach(() => $button.click());
it('shows a loading indicator', () => {
expect($button.className).to.include('is-loading');
});
});
Asserting that something has not happened
Another common scenario is asserting that something has not
happened. If you were to test for this normally (or even with a
convergent assertion above) the test could potentially pass
successfully before a side-effect has time to even happen.
In these scenarios, you want to converge when the state meets an
expectation for a given period of time. In other words, "if this
assertion remains true for X amount of time, this test is considered
to be passing."
@bigtest/mocha
provides an it.always
method to do just this. This
method will run the assertion throughout the entire timeout period
ensuring it never fails. When the assertion does fail, the test
fails. If the assertion never fails, it will pass just after the
timeout period.
describe('clicking my button', () => {
beforeEach(() => $button.click());
it.always('does not navigate away for at least 1 second', () => {
expect(app.location).to.equal('/');
}).timeout(1000);
});
Convergent Hooks
Sometimes you may attempt to perform an async task to find it fails
due to a preconceived state not being met. For example, you can't
click a button if it doesn't exist in the DOM. You may use
@bigtest/convergence
to converge on these states and return
convergences inside of your hooks. The hooks provided by
@bigtest/mocha
will automatically set the timeout and run returned
Convergence
instances.
describe('clicking my button', () => {
beforeEach(() => new Convergence()
.once(() => expect($button).to.exist)
.do(() => $button.click()));
it('shows a loading indicator', () => {
expect($button.className).to.include('is-loading');
});
});
Check out the @bigtest/convergence
API
docs
for working with the Convergence
class.
Pausing Tests
Pausing tests can be very useful when debugging. It allows you to
investigate your application during a critical moment in your testing
suite. Mocha does not have a convinient way to pause tests, but
@bigtest/mocha
helps alleviate this by providing an it.pause
method. It works by setting the current timeout to 0
and gives Mocha
a promise that never resolves. This effectively pauses the entire
suite until you remove it.pause
and restart your tests.
describe('clicking my button', () => {
beforeEach(() => $button.click());
it.pause('shows a loading indicator', () => {
expect($button.className).to.include('is-loading');
});
});