Is it for me?

Aaron Jacobs edited this page Aug 26, 2014 · 6 revisions

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.

New codebases

No DOM manipulation

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.

Some DOM manipulation

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.

Existing codebases

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 to expectEq).

  • 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 an elementsAre 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.