docs: Improve documentation on node test suite.

The most important part of this is encouraging folks to read the
zjquery documentation.
This commit is contained in:
Tim Abbott
2019-04-10 16:34:52 -07:00
parent e16d721921
commit 93b2fa9b7c

View File

@@ -1,7 +1,10 @@
# JavaScript unit tests
As an alternative to the black-box whole-app testing, you can unit test
individual JavaScript files.
Our node-based JavaScript unit tests system is the preferred way to
test JavaScript code in Zulip. We prefer it over the
[Casper black-box whole-app testing](../testing/testing-with-casper.html),
system since it is much (>100x) faster and also easier to do correctly
than the Casper system.
You can run tests as follow:
```
@@ -36,32 +39,46 @@ see if there are corresponding test in `frontend_tests/node_tests`. If
there are, you should strive to follow the patterns of the existing tests
and add your own tests.
## Coverage reports
## How the node tests work
You can automatically generate coverage reports for the JavaScript unit
tests like this:
Unlike the [casper unit tests](../testing/testing-with-casper.html),
which use the `phantomjs` browser connected to a running Zulip
deveopment server, our node unit tests don't have a browser, don't
talk to a server, and generally don't use a complete virtual DOM (a
handful of tests use the `jsdom` library for this purpose) because
those slow down the tests a lot, and often don't add much value.
```
tools/test-js-with-node --coverage
```
Instead, the preferred model for our unit tests is to mock DOM
manipulations (which in Zulip are almost exclusively done via
`jQuery`) using a custom library
[zjquery](https://github.com/zulip/zulip/blob/master/frontend_tests/zjsunit/zjquery.js).
If tests pass, you will get instructions to view coverage reports
in your browser.
The
[unit test file](https://github.com/zulip/zulip/blob/master/frontend_tests/node_tests/zjquery.js)
for `zjquery` is designed to be also serve as nice documentation for
how to use `zjquery`, and is **highly recommended reading** for anyone
working on or debugging the Zulip node tests.
Note that modules that
we don't test *at all* aren't listed in the report, so this tends to
overstate how good our overall coverage is, but it's accurate for
individual files. You can also click a filename to see the specific
statements and branches not tested. 100% branch coverage isn't
necessarily possible, but getting to at least 80% branch coverage is a
good goal.
Conceptually, the `zjquery` library provides minimal versions of most
`jQuery` DOM manipulation functions, and has a convenient system for
letting you setup return values for more complex functions. For
example, if the code you'd like to test calls `$obj.find()`, you can
use `$obj.set_find_results(selector, $value)` to setup `zjquery` so
that calls to `$obj.find(selector)` will return `$value`. See the
unit test file for details.
The `zjquery` library itself is only about 500 lines of code, and can
also be a useful resource if you're having trouble debugging DOM
access in the unit tests.
## Handling dependencies in unit tests
The following scheme helps avoid tests leaking globals between each
other.
You want to categorize each module as follows:
The other big challenge with doing unit tests for a JavaScript project
is that often one wants to limit the scope the production code being
run, just to avoid doing extra setup work that isn't relevant to the
code you're trying to test. For that reason, each unit test file
explicitly declares all of the modules it depends on, with a few
different types of declarations depending on whether we want to:
- Exercise the module's real code for deeper, more realistic testing?
- Stub out the module's interface for more control, speed, and
@@ -75,8 +92,8 @@ like the following toward the top of your test file:
> zrequire('stream_data');
> zrequire('Filter', 'js/filter');
For modules that you want to completely stub out, please use a pattern
like this:
For modules that you want to completely stub out, use a pattern like
this:
> set_global('page_params', {
> email: 'bob@zulip.com'
@@ -85,17 +102,20 @@ like this:
> // then maybe further down
> page_params.email = 'alice@zulip.com';
Finally, there's the hybrid situation, where you want to borrow some of
a module's real functionality but stub out other pieces. Obviously, this
is a pretty strong smell that the other module might be lacking in
cohesion, but that code might be outside your jurisdiction. The pattern
here is this:
One can similarly stub out functions in a module's exported interface
with either `noop` functions or actual code.
Finally, there's the hybrid situation, where you want to borrow some
of a module's real functionality but stub out other pieces. Obviously,
this is a pretty strong code smell that the other module might be
lacking in cohesion, but sometimes it's not worth going down the
rabbit hole of trying to improve that. The pattern here is this:
> // Import real code.
> zrequire('narrow');
> zrequire('narrow_state');
>
> // And later...
> narrow.stream = function () {
> narrow_state.stream = function () {
> return 'office';
> };
@@ -116,3 +136,24 @@ is at the bottom of `foobar.js`:
This means `foobar.js` follow the CommonJS module pattern, so it can be
required in Node.js, which runs our tests.
## Coverage reports
You can automatically generate coverage reports for the JavaScript unit
tests like this:
```
tools/test-js-with-node --coverage
```
If tests pass, you will get instructions to view coverage reports
in your browser.
Note that modules that we don't test *at all* aren't listed in the
report, so this tends to overstate how good our overall coverage is,
but it's accurate for individual files. You can also click a filename
to see the specific statements and branches not tested. 100% branch
coverage isn't necessarily possible, but getting to at least 80%
branch coverage is a good goal.
The overall project goal is to get to 100% node test coverage on all
data/logic modules (UI modules are lower priority for unit testing).