-
Notifications
You must be signed in to change notification settings - Fork 47
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
expectEqorexpectThat, 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
gjstestthat 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
ReturnsCorrectWordsmethod ofGetSomeWordsTestmakes use ofexpectThatwith the matcherelementsArein order to assert something about the contents of an array. This is a more concise way of expressing the same thing using severalexpectEqstatements. 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
AddNumbersAndCallBackTestcreates a mock function and attaches it to theresultCallback_property of itsthisobject. Each test method on the class can accessthis.resultCallback_to get ahold of the mock callback it created. -
The first two test methods of
AddNumbersAndCallBackTestmake use ofexpectCallto 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.ThrowsErrorForStringArgmakes use of another interesting matcher –throwsErrormatches functions which, when invoked, throw an error matching a supplied regular expression. This allows us to assert thatmyproject.addNumbersAndCallBackthrows 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.jsNote 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.