diff --git a/docs/testing/testing-with-node.md b/docs/testing/testing-with-node.md index 5cf22e3b45..48e9847b05 100644 --- a/docs/testing/testing-with-node.md +++ b/docs/testing/testing-with-node.md @@ -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).