Dart Documentationunittest

unittest library

A library for writing dart unit tests.

To import this library, install the unittest package via the pub package manager. See the Getting Started guide for more details.

Concepts

  • Tests: Tests are specified via the top-level function test, they can be organized together using group.

  • Checks: Test expectations can be specified via expect
  • Matchers: expect assertions are written declaratively using the Matcher class.

  • Configuration: The framework can be adapted by calling configure with a Configuration. See the other libraries in the unittest package for alternative implementations of Configuration including compact_vm_config.dart, html_config.dart and html_enhanced_config.dart.

Examples

A trivial test:

import 'package:unittest/unittest.dart';
main() {
  test('this is a test', () {
    int x = 2 + 3;
    expect(x, equals(5));
  });
}

Multiple tests:

import 'package:unittest/unittest.dart';
main() {
  test('this is a test', () {
    int x = 2 + 3;
    expect(x, equals(5));
  });
  test('this is another test', () {
    int x = 2 + 3;
    expect(x, equals(5));
  });
}

Multiple tests, grouped by category:

import 'package:unittest/unittest.dart';
main() {
  group('group A', () {
    test('test A.1', () {
      int x = 2 + 3;
      expect(x, equals(5));
    });
    test('test A.2', () {
      int x = 2 + 3;
      expect(x, equals(5));
    });
  });
  group('group B', () {
    test('this B.1', () {
      int x = 2 + 3;
      expect(x, equals(5));
    });
  });
}

Asynchronous tests: if callbacks expect between 0 and 2 positional arguments, depending on the suffix of expectAsyncX(). expectAsyncX() will wrap a function into a new callback and will not consider the test complete until that callback is run. A count argument can be provided to specify the number of times the callback should be called (the default is 1).

import 'package:unittest/unittest.dart';
import 'dart:isolate';
main() {
  test('callback is executed once', () {
    // wrap the callback of an asynchronous call with [expectAsync0] if
    // the callback takes 0 arguments...
    var timer = Timer.run(expectAsync0(() {
      int x = 2 + 3;
      expect(x, equals(5));
    }));
  });

  test('callback is executed twice', () {
    var callback = expectAsync0(() {
      int x = 2 + 3;
      expect(x, equals(5));
    }, count: 2); // <-- we can indicate multiplicity to [expectAsync0]
    Timer.run(callback);
    Timer.run(callback);
  });
}

expectAsyncX() will wrap the callback code in a try/catch handler to handle exceptions (treated as test failures). There may be times when the number of times a callback should be called is non-deterministic. In this case a dummy callback can be created with expectAsync0((){}) and this can be called from the real callback when it is finally complete. In this case the body of the callback should be protected within a call to guardAsync(); this will ensure that exceptions are properly handled.

A variation on this is expectAsyncUntilX(), which takes a callback as the first parameter and a predicate function as the second parameter; after each time * the callback is called, the predicate function will be called; if it returns false the test will still be considered incomplete.

Test functions can return Futures, which provide another way of doing asynchronous tests. The test framework will handle exceptions thrown by the Future, and will advance to the next test when the Future is complete. It is still important to use expectAsync/guardAsync with any parts of the test that may be invoked from a top level context (for example, with Timer.run()], as the Future exception handler may not capture exceptions in such code.

Note: due to some language limitations we have to use different functions depending on the number of positional arguments of the callback. In the future, we plan to expose a single expectAsync function that can be used regardless of the number of positional arguments. This requires new langauge features or fixes to the current spec (e.g. see Issue 2706).

Meanwhile, we plan to add this alternative API for callbacks of more than 2 arguments or that take named parameters. (this is not implemented yet, but will be coming here soon).

import 'package:unittest/unittest.dart';
import 'dart:isolate';
main() {
  test('callback is executed', () {
    // indicate ahead of time that an async callback is expected.
    var async = startAsync();
    Timer.run(() {
      // Guard the body of the callback, so errors are propagated
      // correctly.
      guardAsync(() {
        int x = 2 + 3;
        expect(x, equals(5));
      });
      // indicate that the asynchronous callback was invoked.
      async.complete();
    });
  });
}

Properties

final TestCase currentTestCase #

TestCase currently being executed.

TestCase get currentTestCase =>
   (_currentTestCaseIndex >= 0 && _currentTestCaseIndex < _testCases.length)
       ? _testCases[_currentTestCaseIndex]
       : null;

const ERROR #

const ERROR = 'error'

const FAIL #

const FAIL  = 'fail'

String groupSep #

Separator used between group names and test names.

String groupSep = ' '

const PASS #

Test case result strings.

const PASS  = 'pass'

final List<TestCase> testCases #

Get the list of tests.

final List<TestCase> testCases = new UnmodifiableListView(_testCases)

Map testState #

A map that can be used to communicate state between a test driver or main() function and the tests, particularly when these two are otherwise independent. For example, a test driver that starts an HTTP server and then runs tests that access that server could use this as a way of communicating the server port to the tests.

Map testState = {}

Configuration get unittestConfiguration #

Configuration used by the unittest library.

Configuration get unittestConfiguration => _config;

void set unittestConfiguration(Configuration value) #

Sets the Configuration used by the unittest library.

Throws a StateError if there is an existing, incompatible value.

void set unittestConfiguration(Configuration value) {
 if(!identical(_config, value)) {
   if(_config != null) {
     throw new StateError('unittestConfiguration has already been set');
   }
   _config = value;
 }
}

Functions

void disableTest(int testId) #

Disable a test by ID.

void disableTest(int testId) => _setTestEnabledState(testId, false);

void enableTest(int testId) #

Enable a test by ID.

void enableTest(int testId) => _setTestEnabledState(testId, true);

void setSoloTest(int id) #

Select a solo test by ID.

void setSoloTest(int id) {
 for (var i = 0; i < _testCases.length; i++) {
   if (_testCases[i].id == id) {
     _soloTest = _testCases[i];
     break;
   }
 }
}

void ensureInitialized() #

Lazily initializes the test library if not already initialized.

void ensureInitialized() {
 _ensureInitialized(true);
}

void registerException(e, [trace]) #

Registers that an exception was caught for the current test.

void registerException(e, [trace]) {
 _registerException(_currentTestCaseIndex, e, trace);
}

dynamicguardAsync(Function tryBody) #

Run tryBody guarded in a try-catch block. If an exception is thrown, it is passed to the corresponding test.

The value returned by tryBody (if any) is returned by guardAsync.

guardAsync(Function tryBody) {
 return _guardAsync(tryBody, null, _currentTestCaseIndex);
}

void runTests() #

Runs all queued tests, one at a time.

void runTests() {
 _ensureInitialized(false);
 _currentTestCaseIndex = 0;
 _currentGroup = '';

 // If we are soloing a test, remove all the others.
 if (_soloTest != null) {
   filterTests((t) => t == _soloTest);
 }

 _config.onStart();

 _defer(() {
   _nextBatch();
 });
}

void filterTests(testFilter) #

Filter the tests. testFilter can be a RegExp, a String or a predicate function. This is different to enabling/disabling tests in that it removes the tests completely.

void filterTests(testFilter) {
 var filterFunction;
 if (testFilter is String) {
   RegExp re = new RegExp(testFilter);
   filterFunction = (t) => re.hasMatch(t.description);
 } else if (testFilter is RegExp) {
   filterFunction = (t) => testFilter.hasMatch(t.description);
 } else if (testFilter is Function) {
   filterFunction = testFilter;
 }
 _testCases.retainWhere(filterFunction);
}

void rerunTests() #

void rerunTests() {
 _uncaughtErrorMessage = null;
 _initialized = true; // We don't want to reset the test array.
 runTests();
}

void tearDown(Function teardownTest) #

Register a tearDown function for a test group. This function will be called after each test in the group is run. Note that if groups are nested only the most locally scoped teardownTest function will be run. setUp and tearDown should be called within the group before any calls to test. The teardownTest function can be asynchronous; in this case it must return a Future.

void tearDown(Function teardownTest) {
 _testTeardown = teardownTest;
}

void setUp(Function setupTest) #

Register a setUp function for a test group. This function will be called before each test in the group is run. Note that if groups are nested only the most locally scoped setUpTest function will be run. setUp and tearDown should be called within the group before any calls to test. The setupTest function can be asynchronous; in this case it must return a Future.

void setUp(Function setupTest) {
 _testSetup = setupTest;
}

void group(String description, void body()) #

Creates a new named group of tests. Calls to group() or test() within the body of the function passed to this will inherit this group's description.

void group(String description, void body()) {
 ensureInitialized();
 // Concatenate the new group.
 final parentGroup = _currentGroup;
 if (_currentGroup != '') {
   // Add a space.
   _currentGroup = '$_currentGroup$groupSep$description';
 } else {
   // The first group.
   _currentGroup = description;
 }

 // Groups can be nested, so we need to preserve the current
 // settings for test setup/teardown.
 Function parentSetup = _testSetup;
 Function parentTeardown = _testTeardown;

 try {
   _testSetup = null;
   _testTeardown = null;
   body();
 } catch (e, trace) {
   var stack = (trace == null) ? '' : ': ${trace.toString()}';
   _uncaughtErrorMessage = "${e.toString()}$stack";
 } finally {
   // Now that the group is over, restore the previous one.
   _currentGroup = parentGroup;
   _testSetup = parentSetup;
   _testTeardown = parentTeardown;
 }
}

Function protectAsync2(Function callback, {String id}) #

Like protectAsync0 but callback should take 2 positional arguments.

Function protectAsync2(Function callback, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke2;
}

Function protectAsync1(Function callback, {String id}) #

Like protectAsync0 but callback should take 1 positional argument.

Function protectAsync1(Function callback, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke1;
}

Function protectAsync0(Function callback, {String id}) #

Wraps the callback in a new function and returns that function. The new function will be able to handle exceptions by directing them to the correct test. This is thus similar to expectAsync0. Use it to wrap any callbacks that might optionally be called but may never be called during the test. callback should take 0 positional arguments (named arguments are not supported). id can be used to identify the callback in error messages (for example if it is called after the test case is complete).

Function protectAsync0(Function callback, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke0;
}

Function expectAsyncUntil2(Function callback, Function isDone, {String id}) #

Like expectAsyncUntil0 but callback should take 2 positional arguments.

Function expectAsyncUntil2(Function callback, Function isDone, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke2;
}

Function expectAsyncUntil1(Function callback, Function isDone, {String id}) #

Like expectAsyncUntil0 but callback should take 1 positional argument.

Function expectAsyncUntil1(Function callback, Function isDone, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke1;
}

Function expectAsyncUntil0(Function callback, Function isDone, {String id}) #

Indicate that callback is expected to be called until isDone returns true. The unittest framework check isDone after each callback and only when it returns true will it continue with the following test. Using expectAsyncUntil0 will also ensure that errors that occur within callback are tracked and reported. callback should take 0 positional arguments (named arguments are not supported). id can be used to identify the callback in error messages (for example if it is called after the test case is complete).

Function expectAsyncUntil0(Function callback, Function isDone, {String id}) {
 return new _SpreadArgsHelper(callback, 0, -1, isDone, id).invoke0;
}

Function expectAsync2(Function callback, {int count: 1, int max: 0, String id}) #

Like expectAsync0 but callback should take 2 positional arguments.

Function expectAsync2(Function callback,
                    {int count: 1, int max: 0, String id}) {
 return new _SpreadArgsHelper(callback, count, max, null, id).invoke2;
}

Function expectAsync1(Function callback, {int count: 1, int max: 0, String id}) #

Like expectAsync0 but callback should take 1 positional argument.

Function expectAsync1(Function callback,
                    {int count: 1, int max: 0, String id}) {
 return new _SpreadArgsHelper(callback, count, max, null, id).invoke1;
}

Function expectAsync0(Function callback, {int count: 1, int max: 0, String id}) #

Indicate that callback is expected to be called a count number of times (by default 1). The unittest framework will wait for the callback to run the specified count times before it continues with the following test. Using expectAsync0 will also ensure that errors that occur within callback are tracked and reported. callback should take 0 positional arguments (named arguments are not supported). id can be used to provide more descriptive error messages if the callback is called more often than expected. max can be used to specify an upper bound on the number of calls; if this is exceeded the test will fail (or be marked as in error if it was already complete). A value of 0 for max (the default) will set the upper bound to the same value as count; i.e. the callback should be called exactly count times. A value of -1 for max will mean no upper bound.

Function expectAsync0(Function callback,
                    {int count: 1, int max: 0, String id}) {
 return new _SpreadArgsHelper(callback, count, max, null, id).invoke0;
}

void solo_test(String spec, TestFunction body) #

Creates a new test case with the given description and body. The description will include the descriptions of any surrounding group() calls.

"solo" means that this will be the only test that is run. All other tests will be skipped. This is a convenience function to let you quickly isolate a single test by adding "solo" before it to temporarily disable all other tests.

void solo_test(String spec, TestFunction body) {
 // TODO(rnystrom): Support multiple solos. If more than one test is solo-ed,
 // all of the solo-ed tests and none of the non-solo-ed ones should run.
 if (_soloTest != null) {
   throw new Exception('Only one test can be soloed right now.');
 }

 ensureInitialized();

 _soloTest = new TestCase._internal(_testCases.length + 1, _fullSpec(spec), body);
 _testCases.add(_soloTest);
}

void test(String spec, TestFunction body) #

Creates a new test case with the given description and body. The description will include the descriptions of any surrounding group() calls.

void test(String spec, TestFunction body) {
 ensureInitialized();
 _testCases.add(new TestCase._internal(_testCases.length + 1, _fullSpec(spec),
                                       body));
}

void logMessage(String message) #

Can be called by tests to log status. Tests should use this instead of print.

void logMessage(String message) =>
   _config.onLogMessage(currentTestCase, message);

Classes

Typedefs