UnitTestsJS

mlunzena: Ich habe dazu bereits einmal Dokumentation geschrieben, allerdings auf englisch. Als Spatz in der Hand bleibt dieser Text zunächst unübersetzt.

Unit Tests JavaScript

Introduction

This wiki page contains a summary of the documentation of QUnit found on:

Citing the introductory words on its homepage:

QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery project to test its code and plugins but is capable of testing any generic JavaScript code (and even capable of testing JavaScript code on the server-side).

There is no easy definition of "unit testing" for JavaScript. Does it include having a DOM library? Does it include a specific browser of some vendor?

Each of these three levels could be tested:

We wanted to have midscale and full tests and thus decided to define JavaScript unit tests as "unit tests involving the DOM library in htmlunit".

How to write a simple JavaScript unit test

The general usage of QUnit is explained at http://docs.jquery.com/QUnit#Using_QUnit

The essential structure of a unit test is:

  • arrange / given
  • action / when
  • assert / then

Assertions

There are three assertions provided by QUnit:

ok(state, [message])
test("a test", function() {
  ok(true, "always fine");
  ok(false, "this assertion fails");
});

This is the most basic assertion function provided by QUnit. It requires a single boolean argument. If this argument resolves to true, this assertion passes. If it resolves to false, this assertion fails. You can give an additional string to show a meaningful explanation, if this assertion fails.

equals(actual, expected, [message])
test("a test", function() {
  var actual = 1;
  equals(actual, 1);
  equals(actual - 1, 0, "this message is added to the assertion result, useful for debugging");
});

equals is equivalent to JUnit's assertEqual (but the order is different). Use this for comparing non-boolean values, as it outputs both values on fail.

same(actual, expected, [message] )
test("same test", function() {
    same(undefined, undefined, "same succeeds");
    same("", 0, "same fails");
});

same is stricter than equals as comparison is done with ===. So null, 0, the empty string "" and undefined are not the same. Moreover it does a deep, recursive comparison instead. This assertion should be used in most cases.

Writing test cases

Assertions are contained in Test cases. Try to keep tests atomic using at most a single assertion per case. This way you will know exactly why a test case fails and you will prevent invalid results from side effects.

Test cases are written by calling the test method:

test("a test", function() {
  ok(true, "always fine");
});

The signature of this method is:

test(name, [expected], test)

  • name is obviously the name of the test.
  • expected is an optional parameter containing the number of assertions expected to run. This is important for asynchronous tests.
  • test is a function literal containing the actual testing code.

Grouping Tests

If you split your test cases keeping them atomic and free of side effects, you will end up with a lot of these. To organize them logically and to be able to run only certain groups of tests, you can employ the module method to group the test cases.

module("group a");
test("a basic test example", function() {
    ok( true, "this test is fine" );
});
test("a basic test example 2", function() {
    ok( true, "this test is fine" );
});
module("group b");
test("a basic test example 3", function() {
    ok( true, "this test is fine" );
});
test("a basic test example 4", function() {
    ok( true, "this test is fine" );
});

After calling module every test case will end up in that group. If you call module again, all following test cases are grouped in that module. Additionally module can be used for setting up fixtures and tearing them down:

module("lifecyle example", {
  setup: function() {
    this.testData = "foobar";
  },
  teardown: function() {
    delete this.testData;
  }
});
test("test with setup and teardown", function() {
  same(this.testData, "foobar");
});

So the signature of this method is:

module(name,

)

  • name is the name of this module.
  • lifecycle is optional and contains setup and teardown callbacks which are run before and after every test in this module.

Testing synchronous callbacks

Whenever you have to tests code with synchronous callbacks, there will be passing tests that should actually fail as the assertion is never evaluated.

test("a test", function() {
    expect(1);
    $("input").myPlugin({
        initialized: function() {
            ok(true, "plugin initialized");
        }
    });
});

For these circumstances you may use the method expect(amount) to assert the number of assertions to get evaluated. expect does nothing for you if you do not test code using callbacks. Note that the second, optional parameter to test does exactly the same thing.

Asynchronous Tests

Unfortunately expect may not help you with asynchronous callbacks which are used by XmlHttpRequests or DOM events. Testing is inherently synchronous and so you have to stop the test runner for a moment until that magic happens and get started afterwards.

test("a test", function() {
    stop(500);
    $.getJSON("/someurl", function(result) {
        equals(result.value, "someExpectedValue");
        start();
    });
});

As you see there are two additional methods stop(

) and start() which allow you to test asynchronous callbacks. Just call stop before asynchronous operations and start after all assertions in order to ask the test runner to move onward. Obviously you risk, that start may get never be called. To mitigate that fact you may provide a number of milliseconds as an timeout to stop to make sure that the test runner continues after at most this given amount of time.

test("a test", function() {
    stop(500);
    $.getJSON("/someurl", function(result) {
        equals(result.value, "someExpectedValue");
        start();
    });
});

Try to avoid that parameter to prevent hunting for false negatives.

Letzte Änderung am May 17, 2011, at 11:39 AM von mlunzena.