unittest library
A library for writing dart unit tests.
To import this library, use the pub package manager. Create a pubspec.yaml file in your project and add a dependency on unittest with the following lines:
dependencies: unittest: any
Then run 'pub install' from your project directory or using the DartEditor.
Please see Pub Getting Started for more details about the pub package manager.
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 usingMatcher
s -
Configuration: The framework can be adapted by calling configure with a Configuration. Common configurations can be found in this package under: 'dom\config.dart' (deprecated), 'html\config.dart' (for running tests compiled to Javascript in a browser), and 'vm\_config.dart' (for running native Dart tests on the VM).
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.
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 Configuration config #
Configuration get config => _config;
const ERROR #
ERROR = 'error'
const FAIL #
FAIL = 'fail'
String groupSep #
groupSep = ' '
const PASS #
PASS = 'pass'
final testCases #
Get the list of tests.
get testCases => _tests;
Map testState #
testState = {}
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 < _tests.length; i++) { if (_tests[i].id == id) { _soloTest = _tests[i]; break; } } }
ensureInitialized() #
Lazily initializes the test library if not already initialized.
ensureInitialized() { if (_initialized) { return; } _initialized = true; // Hook our async guard into the matcher library. wrapAsync = expectAsync1; _tests = <TestCase>[]; _testRunner = _nextBatch; _uncaughtErrorMessage = null; if (_config == null) { _config = new Configuration(); } _config.onInit(); if (_config.autoStart) { // Immediately queue the suite up. It will run after a timeout (i.e. after // main() has returned). _defer(runTests); } }
void fail(String message) #
void fail(String message) { throw new ExpectException(message); }
registerException(e, [trace]) #
Registers that an exception was caught for the current test.
registerException(e, [trace]) { _registerException(_currentTest, e, trace); }
guardAsync(tryBody, [finallyBody, testNum = -1]) #
Run tryBody guarded in a try-catch block. If an exception is thrown, update the _currentTest status accordingly.
guardAsync(tryBody, [finallyBody, testNum = -1]) { if (testNum < 0) testNum = _currentTest; try { return tryBody(); } catch (e, trace) { _registerException(testNum, e, trace); } finally { if (finallyBody != null) finallyBody(); } }
runTests() #
Runs all queued tests, one at a time.
runTests() { _currentTest = 0; _currentGroup = ''; // If we are soloing a test, remove all the others. if (_soloTest != null) { filterTests((t) => t == _soloTest); } _config.onStart(); _defer(() { _testRunner(); }); }
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; } _tests = _tests.where(filterFunction).toList(); }
rerunTests() #
rerunTests() { _uncaughtErrorMessage = null; _initialized = true; // We don't want to reset the test array. runTests(); }
void callbackDone() #
Temporary hack: expose old API. TODO(gram) remove this when WebKit tests are working with new framework
void callbackDone() { _handleCallbackFunctionComplete(_currentTest); }
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) #
Like protectAsync0 but callback should take 2 positional arguments.
Function protectAsync2(Function callback) { return new _SpreadArgsHelper.optionalCalls(callback).invoke2; }
Function protectAsync1(Function callback) #
Like protectAsync0 but callback should take 1 positional argument.
Function protectAsync1(Function callback) { return new _SpreadArgsHelper.optionalCalls(callback).invoke1; }
Function protectAsync0(Function callback) #
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).
Function protectAsync0(Function callback) { return new _SpreadArgsHelper.optionalCalls(callback).invoke0; }
Function expectAsyncUntil2(Function callback, Function isDone) #
Like expectAsyncUntil0 but callback should take 2 positional arguments.
Function expectAsyncUntil2(Function callback, Function isDone) { return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke2; }
Function expectAsyncUntil1(Function callback, Function isDone) #
Like expectAsyncUntil0 but callback should take 1 positional argument.
Function expectAsyncUntil1(Function callback, Function isDone) { return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke1; }
Function expectAsyncUntil0(Function callback, Function isDone) #
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).
Function expectAsyncUntil0(Function callback, Function isDone) { return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke0; }
Function expectAsync2(Function callback, {int count: 1, String id}) #
Like expectAsync0 but callback should take 2 positional arguments.
Function expectAsync2(Function callback, {int count: 1, String id}) { return new _SpreadArgsHelper. fixedCallCount(callback, count, id).invoke2; }
Function expectAsync1(Function callback, {int count: 1, String id}) #
Like expectAsync0 but callback should take 1 positional argument.
Function expectAsync1(Function callback, {int count: 1, String id}) { return new _SpreadArgsHelper. fixedCallCount(callback, count, id).invoke1; }
Function expectAsync0(Function callback, {int count: 1, 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.
Function expectAsync0(Function callback, {int count: 1, String id}) { return new _SpreadArgsHelper. fixedCallCount(callback, count, 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(_tests.length + 1, _fullSpec(spec), body, 0); _tests.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(); _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); }
void logMessage(String message) #
void logMessage(String message) => _config.logMessage(message);
Configuration configure(Configuration config) #
Set the Configuration used by the unittest library. Returns any previous configuration. TODO: consider deprecating in favor of a setter now we have a getter.
Configuration configure(Configuration config) { Configuration _oldConfig = _config; _config = config; return _oldConfig; }