-
Notifications
You must be signed in to change notification settings - Fork 48
Getting started
This document contains a tutorial for writing your first test using the Google
JS Test framework. If you haven't yet, you'll need to first follow the
instructions on the Installing page to get the gjstest
tool set up on your
system.
<wiki:toc />
Google JS Test consists of the following parts:
-
A unit testing framework that allows you to register test functions that express expectations using functions like
expectEq
orexpectThat
, and matchers likelessThan(2)
orcontainsRegExp(/taco/)
. -
Built-in mocking capabilities, allowing you to create mock functions or class instances whose behavior you control in the test. Any expectations registered with the mocking framework are automatically checked by the unit testing framework.
-
A tool called
gjstest
that allows you to run the tests you write and see whether they passed or failed.
The sections below take you through writing a toy class and testing it with Google JS Test.
For the purposes of this tutorial, let's assume that we're working on a file
called some_functions.js
with containing several functions we want to test.
Create the file some_functions.js
and put the following code in it:
// Return some interesting words.
myproject.getSomeWords = function() {
return ['Hello', 'World'];
};
// Add the supplied numbers and then call back with the result.
myproject.addNumbersAndCallBack = function(a, b, resultCallback) {
var result = a + b;
resultCallback(a + b);
};
These are just toy functions; you'd probably have something more interesting in your project.
Note that the object myproject
(serving as a namespace here) has not been
defined within this file. Let's suppose its definition is in a separate file, so
that it can be shared with other code in your project. Add the following to a
file called namespace.js
:
var myproject = {};
Now we want to write some tests for the functions defined in
some_functions.js
, which you created above. Create a new file called
some_functions_test.js
and put the following into it. (Note that there's
nothing special about the filename some_functions_test.js
; it's just a
convention.)
//////////////////////////
// getSomeWords
//////////////////////////
function GetSomeWordsTest() {}
registerTestSuite(GetSomeWordsTest);
addTest(GetSomeWordsTest, function ReturnsCorrectWords() {
var words = myproject.getSomeWords();
// Assert directly what the words should be.
expectThat(words, elementsAre(['Hello', 'world']));
// Note that you could have also done so as follows, but it doesn't give
// error messages that are as nice.
expectEq(2, words.length);
expectEq('Hello', words[0]);
expectEq('world', words[1]);
});
//////////////////////////
// addNumbersAndCallBack
//////////////////////////
function AddNumbersAndCallBackTest() {
// Create a mock function and store it in a place accessible to the test
// functions below. A new one will be created for each test method.
this.resultCallback_ = createMockFunction();
}
registerTestSuite(AddNumbersAndCallBackTest);
addTest(AddNumbersAndCallBackTest, function HandlesPositiveNumbers() {
var a = 17;
var b = 23;
// Assert that the mock callback will created above will be called with the
// appropriate result.
expectCall(this.resultCallback_)(40);
// Call the function being tested with the appropriate arguments, including
// our mock callback.
myproject.addNumbersAndCallBack(a, b, this.resultCallback_);
});
addTest(AddNumbersAndCallBackTest, function HandlesNegativeNumbers() {
var a = -17;
var b = -5;
expectCall(this.resultCallback_)(-22);
myproject.addNumbersAndCallBack(a, b, this.resultCallback_);
});
addTest(AddNumbersAndCallBackTest, function ThrowsErrorForStringArg() {
// Make sure that if we give a string instead of a number for the first
// argument, the function under test throws an appropriate error.
var trivialCallback = function() {};
var callWithString =
function() {
myproject.addNumbersAndCallBack('foo', 17, trivialCallback);
};
expectThat(callWithString, throwsError(/TypeError.*must be a number/));
});
This file defines two test suites, one for each function we're testing. A test
suite is a way to group logically related tests that may need to share setup
code. For example, the constructor of AddNumbersAndCallBackTest
creates a mock
function that can be used as a callback in its test methods if desired.
Notice a few things about these tests:
-
The
ReturnsCorrectWords
method ofGetSomeWordsTest
makes use ofexpectThat
with the matcherelementsAre
in order to assert something about the contents of an array. This is a more concise way of expressing the same thing using severalexpectEq
statements. It also offers the advantage of better error output in the event of a failure; you'll see an example of this below. See the Matchers page for more info about matchers. -
The constructor for
AddNumbersAndCallBackTest
creates a mock function and attaches it to theresultCallback_
property of itsthis
object. Each test method on the class can accessthis.resultCallback_
to get ahold of the mock callback it created. -
The first two test methods of
AddNumbersAndCallBackTest
make use ofexpectCall
to express that the mock callback should be called by the function being tested with a particular value. If that doesn't happen, the test will fail. See the Mocking page for more info on mocking, including topics like how to define actions your mock functions should perform. -
AddNumbersAndCallBackTest.ThrowsErrorForStringArg
makes use of another interesting matcher –throwsError
matches functions which, when invoked, throw an error matching a supplied regular expression. This allows us to assert thatmyproject.addNumbersAndCallBack
throws an appropriate error when we give it a bad argument.
Now we're ready to run the tests we wrote above, making sure that the functions
we're testing do the write thing. We do this using the gjstest
tool, which we
must tell where to find the code. Run the command below:
gjstest --js_files=namespace.js,some_functions.js,some_functions_test.js
Note that we have to give all of the JS files needed by our test and the code it
is testing, and we have to do so in order. If we left out namespace.js
or put
it after some_functions.js
, we'd find that the first time our code accesses
myproject
it gets an undefined object error.
You should see output like the following:
[----------]
[ RUN ] GetSomeWordsTest.ReturnsCorrectWords
some_functions_test.js:12
Expected: is an array or Arguments object of length 2 with elements matching: [ 'Hello', 'world' ]
Actual: [ 'Hello', 'World' ], whose element 1 doesn't match
some_functions_test.js:18
Expected: 'world'
Actual: 'World'
[ FAILED ] GetSomeWordsTest.ReturnsCorrectWords (4 ms)
[----------]
[----------]
[ RUN ] AddNumbersAndCallBackTest.HandlesPositiveNumbers
[ OK ] AddNumbersAndCallBackTest.HandlesPositiveNumbers (1 ms)
[ RUN ] AddNumbersAndCallBackTest.HandlesNegativeNumbers
[ OK ] AddNumbersAndCallBackTest.HandlesNegativeNumbers (0 ms)
[ RUN ] AddNumbersAndCallBackTest.ThrowsErrorForStringArg
some_functions_test.js:64
Expected: is a function that throws an error matching /TypeError.*must be a number/
Actual: function (), which threw no errors
[ FAILED ] AddNumbersAndCallBackTest.ThrowsErrorForStringArg (0 ms)
[----------]
[ FAILED ]
It turns out that we have some bugs in our code. Notice how the first error
message output in GetSomeWordsTest.ReturnsCorrectWords
gives nicer output than
the second—it shows the entire array, which is extra important if the array is
of the wrong length.
Go ahead and fix these bugs, it's left as an exercise to the reader. You'll want
to fix the capitilization on the second word returned by getSomeWords
, and add
type checking logic to addNumbersAndCallBack
. Once you do, you should be able
to run the gjstest
command given above again, and see that your tests pass.