-
Notifications
You must be signed in to change notification settings - Fork 57
Is it for me?
Whether you should use Google JS Test and how easy it will be depends on your situation. Here's a summary:
-
For a new codebase, use Google JS Test! It's trivial for new pure JS code, and still pretty easy for new code that uses the DOM.
-
For an existing codebase:
-
For the most part it's not too hard to convert existing tests of pure JS code to gjstest.
-
Converting tests that touch the DOM directly, rather than through a mockable function, requires some refactoring. This may or may not be worth the cost, depending on your situation.
-
Note that there's no rule that says you must use Google JS Test for all or none of your tests. Use it where appropriate, and don't use it when you can't or don't want to. You'll still get fast, stable, easy to read tests where you do use it.
Google JS Test runs tests directly in V8, Google's open source JavaScript engine (the one behind Chrome's fast JS execution speed); there is no browser involved. This means that tests start up and execute extremely quickly, but the trade-off is that the other parts of the browser, in particular the DOM, are not available to the test.
As a result, a requirement for using Google JS Test for a test is that the test and the code it executes be composed of pure JS. That's not to say that you can't test code that manipulates the DOM – you can still do so by passing in mock implementations of DOM manipulation functions in your tests. This has other benefits too; see below.
If your test is for code that doesn't touch the DOM or other browser-specific
features (e.g. no references to window
or document
), congratulations! It's
very easy to use Google Js Test; simply take a look at the [Getting started][]
page, and see the Matchers and Mocking pages for more detail.
If the code you're working on needs to manipulate the DOM or otherwise access
properties on the global window
or document
objects, you can structure your
code in such a way that you can use mock implementations in your Google JS Test
code (or indeed any other kind of test).
For example, suppose you want to write a function that writes an error message
into a div
. As a first pass, you might write the function as follows:
function writeErrorMessage(error, elementName) {
// Compose a full error message.
fullError = 'An error has occurred: ' + error +
'Please try reloading the page.';
// Write out the error message.
document.getElementsByName(elementName).innerText = fullError;
}
Notice the function calls document.getElementsByName
to find a particular
element in the DOM. This won't work in Google JS Test unless you make some
effort to fill the global document
object with mock functions, then have those
functions return mock elements.
Instead, you can write the function to take the element to write to directly:
function writeErrorMessage(error, element) {
...
// Write out the error message.
element.innerText = fullError;
}
The function's implementation becomes simpler, and the test you need to write
for it also becomes simpler. You can simply pass in an empty object and make
sure that it comes out the other end with the appropriate innerText
property.
Eventually someone will have to fetch elements from the DOM if they are to be
written to, but that can be isolated into a single place that is either tested
using Google JS Test with the methods mentioned above, or tested in some other
way. (In fact, it can be left to integration testing if appropriate.)
This example was just for illustration, but the technique applies to more
complicated situations as well. For example, you may have a class's constructor
take functions like setTimeout
or createImage
. In production these would
point to the real functions in the DOM or functions that wrap real functionality
in the DOM (perhaps with cleverness like a cache of images or IE6 transparency
workarounds). In tests, use Google JS Test's built-in mocking support to assert
only what you need to about how your class interacts with these functions,
without any real implementation or cleverness geting in the way or making your
test brittle.
Here are some more resources on dependency injection, the name of this general technique:
-
A post by Miško Hevery about why you should inject objects instead of creating them in your class or function.
-
Another post by Miško Hevery dispelling some common misconceptions about dependency injection.
As with many subjects, large existing codebases are tougher than new or small ones. The principles discussed above still apply, however.
If you're writing a brand new class or function or any other chunk of code that has a well-defined interface between it and the rest of the codebase, you're basically working on a new codebase. See the tips above for how to get the most out of Google JS Test.
If you're working on an existing class or large function that is already tested
with a different technology, you can still consider converting to Google JS
Test. If the class is pure JS (no calls to document
or window
), this isn't
too painful:
-
Do the find and replace work necessary to change any cosmetic differences between Google JS Test and your existing tests (e.g. changing
assertEquals
toexpectEq
). -
If your tests use another mocking framework, convert the code that creates mocks and set up expectations.
-
Optionally, update your tests to use Google JS Test's system of expressive matchers instead of its existing assertions. For example, replace multi-line assertions about arrays with a single
expectThat
call with anelementsAre
matcher. This is not necessary, but will give you nicer output if your tests fail.
If your test does do DOM or other browser manipulation, in addition to the points above you'll need to do some further refactoring work if you want to use Google JS Test. See the discussion about DOM manipulation in the section on new codebases above.