diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8210d913467fd08e82e5eecb1eb0eea5ebeccf8..be183d2c7a7cd084d8b2bb41125ce12414059b08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,10 @@
 * Removed several members from `SimpleConfiguration` that relied on removed
   functionality: `onExpectFailure`, `stopTestOnExpectFailure`, and 'name'.
 
+##0.11.5+1
+
+* Internal code cleanups and documentation improvements.
+
 ##0.11.5
 
 * Bumped the version constraint for `matcher`.
diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart
index f4a28c989c73067c370ad45524ab30ff4861bc4f..d3759667241e48d2bdadbcb66f4ba019252eeef5 100644
--- a/lib/src/configuration.dart
+++ b/lib/src/configuration.dart
@@ -4,12 +4,12 @@
 
 library unittest.configuration;
 
-import 'package:unittest/unittest.dart' show TestCase, SimpleConfiguration;
+import 'simple_configuration.dart';
+import 'test_case.dart';
 
 /// Describes the interface used by the unit test system for communicating the
 /// results of a test run.
 abstract class Configuration {
-
   /// Creates an instance of [SimpleConfiguration].
   factory Configuration() => new SimpleConfiguration();
 
@@ -18,9 +18,11 @@ abstract class Configuration {
   /// For use by subclasses which wish to implement only a subset of features.
   Configuration.blank();
 
-  /// If [:true:], tests are started automatically. Otherwise [runTests]
-  /// must be called explicitly after tests are set up.
-  bool get autoStart => true;
+  /// If `true`, tests are started automatically once they're finished being
+  /// defined.
+  ///
+  /// Otherwise, [runTests] must be called explicitly after tests are set up.
+  final autoStart = true;
 
   /// How long a [TestCase] can run before it is considered an error.
   /// A [timeout] value of [:null:] means that the limit is infinite.
diff --git a/lib/src/expected_function.dart b/lib/src/expected_function.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7373c7550829fdb9b010a9a71de3ef77bf9f65a4
--- /dev/null
+++ b/lib/src/expected_function.dart
@@ -0,0 +1,203 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library unittest.expected_function;
+
+import '../unittest.dart';
+
+import 'internal_test_case.dart';
+
+/// An object used to detect unpassed arguments.
+const _PLACEHOLDER = const Object();
+
+// Functions used to check how many arguments a callback takes.
+typedef _Func0();
+typedef _Func1(a);
+typedef _Func2(a, b);
+typedef _Func3(a, b, c);
+typedef _Func4(a, b, c, d);
+typedef _Func5(a, b, c, d, e);
+typedef _Func6(a, b, c, d, e, f);
+
+typedef bool _IsDoneCallback();
+
+/// A wrapper for a function that ensures that it's called the appropriate
+/// number of times.
+///
+/// The containing test won't be considered to have completed successfully until
+/// this function has been called the appropriate number of times.
+///
+/// The wrapper function is accessible via [func]. It supports up to six
+/// optional and/or required positional arguments, but no named arguments.
+class ExpectedFunction {
+  /// The wrapped callback.
+  final Function _callback;
+
+  /// The minimum number of calls that are expected to be made to the function.
+  ///
+  /// If fewer calls than this are made, the test will fail.
+  final int _minExpectedCalls;
+
+  /// The maximum number of calls that are expected to be made to the function.
+  ///
+  /// If more calls than this are made, the test will fail.
+  final int _maxExpectedCalls;
+
+  /// A callback that should return whether the function is not expected to have
+  /// any more calls.
+  ///
+  /// This will be called after every time the function is run. The test case
+  /// won't be allowed to terminate until it returns `true`.
+  ///
+  /// This may be `null`. If so, the function is considered to be done after
+  /// it's been run once.
+  final _IsDoneCallback _isDone;
+
+  /// A descriptive name for the function.
+  final String _id;
+
+  /// An optional description of why the function is expected to be called.
+  ///
+  /// If not passed, this will be an empty string.
+  final String _reason;
+
+  /// The number of times the function has been called.
+  int _actualCalls = 0;
+
+  /// The test case in which this function was wrapped.
+  final InternalTestCase _testCase;
+
+  /// Whether this function has been called the requisite number of times.
+  bool _complete;
+
+  /// Wraps [callback] in a function that asserts that it's called at least
+  /// [minExpected] times and no more than [maxExpected] times.
+  ///
+  /// If passed, [id] is used as a descriptive name fo the function and [reason]
+  /// as a reason it's expected to be called. If [isDone] is passed, the test
+  /// won't be allowed to complete until it returns `true`.
+  ExpectedFunction(Function callback, int minExpected, int maxExpected,
+      {String id, String reason, bool isDone()})
+      : this._callback = callback,
+        _minExpectedCalls = minExpected,
+        _maxExpectedCalls = (maxExpected == 0 && minExpected > 0)
+            ? minExpected
+            : maxExpected,
+        this._isDone = isDone,
+        this._reason = reason == null ? '' : '\n$reason',
+        this._testCase = currentTestCase as InternalTestCase,
+        this._id = _makeCallbackId(id, callback) {
+    ensureInitialized();
+    if (_testCase == null) {
+      throw new StateError("No valid test. Did you forget to run your test "
+          "inside a call to test()?");
+    }
+
+    if (isDone != null || minExpected > 0) {
+      _testCase.callbackFunctionsOutstanding++;
+      _complete = false;
+    } else {
+      _complete = true;
+    }
+  }
+
+  /// Tries to find a reasonable name for [callback].
+  ///
+  /// If [id] is passed, uses that. Otherwise, tries to determine a name from
+  /// calling `toString`. If no name can be found, returns the empty string.
+  static String _makeCallbackId(String id, Function callback) {
+    if (id != null) return "$id ";
+
+    // If the callback is not an anonymous closure, try to get the
+    // name.
+    var toString = callback.toString();
+    var prefix = "Function '";
+    var start = toString.indexOf(prefix);
+    if (start == -1) return '';
+
+    start += prefix.length;
+    var end = toString.indexOf("'", start);
+    if (end == -1) return '';
+    return "${toString.substring(start, end)} ";
+  }
+
+  /// Returns a function that has the same number of positional arguments as the
+  /// wrapped function (up to a total of 6).
+  Function get func {
+    if (_callback is _Func6) return _max6;
+    if (_callback is _Func5) return _max5;
+    if (_callback is _Func4) return _max4;
+    if (_callback is _Func3) return _max3;
+    if (_callback is _Func2) return _max2;
+    if (_callback is _Func1) return _max1;
+    if (_callback is _Func0) return _max0;
+
+    throw new ArgumentError(
+        'The wrapped function has more than 6 required arguments');
+  }
+
+  // This indirection is critical. It ensures the returned function has an
+  // argument count of zero.
+  _max0() => _max6();
+
+  _max1([a0 = _PLACEHOLDER]) => _max6(a0);
+
+  _max2([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER]) => _max6(a0, a1);
+
+  _max3([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER]) =>
+      _max6(a0, a1, a2);
+
+  _max4([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
+      a3 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3);
+
+  _max5([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
+      a3 = _PLACEHOLDER, a4 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3, a4);
+
+  _max6([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
+      a3 = _PLACEHOLDER, a4 = _PLACEHOLDER, a5 = _PLACEHOLDER]) =>
+      _run([a0, a1, a2, a3, a4, a5].where((a) => a != _PLACEHOLDER));
+
+  /// Runs the wrapped function with [args] and returns its return value.
+  ///
+  /// This will pass any errors on to [_testCase] and return `null`.
+  _run(Iterable args) {
+    try {
+      _actualCalls++;
+      if (_testCase.isComplete) {
+        // Don't run the callback if the test is done. We don't throw here as
+        // this is not the current test, but we do mark the old test as having
+        // an error if it previously passed.
+        if (_testCase.result == PASS) {
+          _testCase.error(
+              'Callback ${_id}called ($_actualCalls) after test case '
+              '${_testCase.description} had already been marked as '
+              '${_testCase.result}.$_reason');
+        }
+        return null;
+      } else if (_maxExpectedCalls >= 0 && _actualCalls > _maxExpectedCalls) {
+        throw new TestFailure('Callback ${_id}called more times than expected '
+                              '($_maxExpectedCalls).$_reason');
+      }
+
+      return Function.apply(_callback, args.toList());
+    } catch (error, stackTrace) {
+      _testCase.registerException(error, stackTrace);
+      return null;
+    } finally {
+      _afterRun();
+    }
+  }
+
+  /// After each time the function is run, check to see if it's complete.
+  void _afterRun() {
+    if (_complete) return;
+    if (_minExpectedCalls > 0 && _actualCalls < _minExpectedCalls) return;
+    if (_isDone != null && !_isDone()) return;
+
+    // Mark this callback as complete and remove it from the test case's
+    // oustanding callback count; if that hits zero the test is done.
+    _complete = true;
+    _testCase.markCallbackComplete();
+  }
+}
diff --git a/lib/src/group_context.dart b/lib/src/group_context.dart
index d44b6cd3fec4a21a7c82ab2795b99fa5cd06e5ce..78f347b4d32328caddb35b7ab6f542de4204d5f2 100644
--- a/lib/src/group_context.dart
+++ b/lib/src/group_context.dart
@@ -1,65 +1,75 @@
-part of unittest;
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library unittest.group_context;
+
+import 'dart:async';
+
+import '../unittest.dart';
 
 /// Setup and teardown functions for a group and its parents, the latter
 /// for chaining.
-class _GroupContext {
-  final _GroupContext parent;
+class GroupContext {
+  /// The parent context, or `null`.
+  final GroupContext parent;
+
+  /// Whether this is the root context.
+  bool get isRoot => parent == null;
 
   /// Description text of the current test group.
   final String _name;
 
-  /// Setup function called before each test in a group.
-  Function _testSetup;
-
-  get testSetup => _testSetup;
-
-  get parentSetup => (parent == null) ? null : parent.testSetup;
+  /// The set-up function called before each test in a group.
+  Function get testSetUp => _testSetUp;
+  Function _testSetUp;
 
-  set testSetup(Function setup) {
-    var preSetup = parentSetup;
-    if (preSetup == null) {
-      _testSetup = setup;
-    } else {
-      _testSetup = () {
-        var f = preSetup();
-        if (f is Future) {
-          return f.then((_) => setup());
-        } else {
-          return setup();
-        }
-      };
+  set testSetUp(Function setUp) {
+    if (parent == null || parent.testSetUp == null) {
+      _testSetUp = setUp;
+      return;
     }
-  }
-
-  /// Teardown function called after each test in a group.
-  Function _testTeardown;
 
-  get testTeardown => _testTeardown;
+    _testSetUp = () {
+      var f = parent.testSetUp();
+      if (f is Future) {
+        return f.then((_) => setUp());
+      } else {
+        return setUp();
+      }
+    };
+  }
 
-  get parentTeardown => (parent == null) ? null : parent.testTeardown;
+  /// The tear-down function called after each test in a group.
+  Function get testTearDown => _testTearDown;
+  Function _testTearDown;
 
-  set testTeardown(Function teardown) {
-    var postTeardown = parentTeardown;
-    if (postTeardown == null) {
-      _testTeardown = teardown;
-    } else {
-      _testTeardown = () {
-        var f = teardown();
-        if (f is Future) {
-          return f.then((_) => postTeardown());
-        } else {
-          return postTeardown();
-        }
-      };
+  set testTearDown(Function tearDown) {
+    if (parent == null || parent.testTearDown == null) {
+      _testTearDown = tearDown;
+      return;
     }
+
+    _testTearDown = () {
+      var f = tearDown();
+      if (f is Future) {
+        return f.then((_) => parent.testTearDown());
+      } else {
+        return parent.testTearDown();
+      }
+    };
   }
 
-  String get fullName => (parent == null || parent == _environment.rootContext)
-      ? _name
-      : "${parent.fullName}$groupSep$_name";
+  /// Returns the fully-qualified name of this context.
+  String get fullName =>
+      (isRoot || parent.isRoot) ? _name : "${parent.fullName}$groupSep$_name";
+
+  GroupContext.root()
+      : parent = null,
+        _name = '';
 
-  _GroupContext([this.parent, this._name = '']) {
-    _testSetup = parentSetup;
-    _testTeardown = parentTeardown;
+  GroupContext(this.parent, this._name) {
+    _testSetUp = parent.testSetUp;
+    _testTearDown = parent.testTearDown;
   }
 }
diff --git a/lib/src/internal_test_case.dart b/lib/src/internal_test_case.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1227eea2f58146d96f86632a9919c9e0190a1c15
--- /dev/null
+++ b/lib/src/internal_test_case.dart
@@ -0,0 +1,227 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library unittest.internal_test_case;
+
+import 'dart:async';
+
+import '../unittest.dart';
+import 'test_environment.dart';
+import 'utils.dart';
+
+/// An implementation of [TestCase] that exposes internal properties for other
+/// unittest use.
+class InternalTestCase implements TestCase {
+  final int id;
+  final String description;
+
+  /// The setup function to call before the test, if any.
+  Function _setUp;
+
+  /// The teardown function to call after the test, if any.
+  Function _tearDown;
+
+  /// The body of the test case.
+  TestFunction _testFunction;
+
+  /// Remaining number of callback functions that must reach a 'done' state
+  /// before the test completes.
+  int callbackFunctionsOutstanding = 0;
+
+  /// The error or failure message for the tests.
+  ///
+  /// Initially an empty string.
+  String message = '';
+
+  /// The result of the test case.
+  ///
+  /// If the test case has is completed, this will be one of [PASS], [FAIL], or
+  /// [ERROR]. Otherwise, it will be `null`.
+  String result;
+
+  /// Returns whether this test case passed.
+  bool get passed => result == PASS;
+
+  /// The stack trace for the error that caused this test case to fail, or
+  /// `null` if it succeeded.
+  StackTrace stackTrace;
+
+  /// The name of the group within which this test is running.
+  final String currentGroup;
+
+  /// The time the test case started running.
+  ///
+  /// `null` if the test hasn't yet begun running.
+  DateTime get startTime => _startTime;
+  DateTime _startTime;
+
+  /// The amount of time the test case took.
+  ///
+  /// `null` if the test hasn't finished running.
+  Duration get runningTime => _runningTime;
+  Duration _runningTime;
+
+  /// Whether this test is enabled.
+  ///
+  /// Disabled tests won't be run.
+  bool enabled = true;
+
+  /// A completer that will complete when the test is finished.
+  ///
+  /// This is only non-`null` when outstanding callbacks exist.
+  Completer _testComplete;
+
+  /// Whether this test case has finished running.
+  bool get isComplete => !enabled || result != null;
+
+  InternalTestCase(this.id, this.description, this._testFunction)
+      : currentGroup = environment.currentContext.fullName,
+        _setUp = environment.currentContext.testSetUp,
+        _tearDown = environment.currentContext.testTearDown;
+
+  /// A function that returns another function to handle errors from [Future]s.
+  ///
+  /// [stage] is a string description of the stage of testing that failed.
+  Function _errorHandler(String stage) => (e, stack) {
+    if (stack == null && e is Error) {
+      stack = e.stackTrace;
+    }
+    if (result == null || result == PASS) {
+      if (e is TestFailure) {
+        fail("$e", stack);
+      } else {
+        error("$stage failed: Caught $e", stack);
+      }
+    }
+  };
+
+  /// Performs any associated [_setUp] function and runs the test.
+  ///
+  /// Returns a [Future] that can be used to schedule the next test. If the test
+  /// runs to completion synchronously, or is disabled, null is returned, to
+  /// tell unittest to schedule the next test immediately.
+  Future run() {
+    if (!enabled) return new Future.value();
+
+    result = stackTrace = null;
+    message = '';
+
+    // Avoid calling [new Future] to avoid issue 11911.
+    return new Future.value().then((_) {
+      if (_setUp != null) return _setUp();
+    }).catchError(_errorHandler('Setup')).then((_) {
+      // Skip the test if setup failed.
+      if (result != null) return new Future.value();
+      config.onTestStart(this);
+      _startTime = new DateTime.now();
+      _runningTime = null;
+      callbackFunctionsOutstanding++;
+      var testReturn = _testFunction();
+      // If _testFunction() returned a future, we want to wait for it like we
+      // would a callback, so if a failure occurs while waiting, we can abort.
+      if (testReturn is Future) {
+        callbackFunctionsOutstanding++;
+        testReturn
+            .catchError(_errorHandler('Test'))
+            .whenComplete(markCallbackComplete);
+      }
+    }).catchError(_errorHandler('Test')).then((_) {
+      markCallbackComplete();
+      if (result == null) {
+        // Outstanding callbacks exist; we need to return a Future.
+        _testComplete = new Completer();
+        return _testComplete.future.whenComplete(() {
+          if (_tearDown != null) {
+            return _tearDown();
+          }
+        }).catchError(_errorHandler('Teardown'));
+      } else if (_tearDown != null) {
+        return _tearDown();
+      }
+    }).catchError(_errorHandler('Teardown')).whenComplete(() {
+      _setUp = null;
+      _tearDown = null;
+      _testFunction = null;
+    });
+  }
+
+  /// Marks the test as having completed with [testResult], which should be one
+  /// of [PASS], [FAIL], or [ERROR].
+  void _complete(String testResult,
+      [String messageText = '', StackTrace stack]) {
+    if (runningTime == null) {
+      // The startTime can be `null` if an error happened during setup. In this
+      // case we simply report a running time of 0.
+      if (startTime != null) {
+        _runningTime = new DateTime.now().difference(startTime);
+      } else {
+        _runningTime = const Duration(seconds: 0);
+      }
+    }
+    _setResult(testResult, messageText, stack);
+    if (_testComplete != null) {
+      var t = _testComplete;
+      _testComplete = null;
+      t.complete(this);
+    }
+  }
+
+  // Sets [this]'s fields to reflect the test result, and notifies the current
+  // configuration that the test has completed.
+  //
+  // Returns true if this is the first time the result has been set.
+  void _setResult(String testResult, String messageText, StackTrace stack) {
+    message = messageText;
+    stackTrace = getTrace(stack, formatStacks, filterStacks);
+    if (stackTrace == null) stackTrace = stack;
+    if (result == null) {
+      result = testResult;
+      config.onTestResult(this);
+    } else {
+      result = testResult;
+      config.onTestResultChanged(this);
+    }
+  }
+
+  /// Marks the test as having passed.
+  void pass() {
+    _complete(PASS);
+  }
+
+  void registerException(error, [StackTrace stackTrace]) {
+    var message = error is TestFailure ? error.message : 'Caught $error';
+    if (result == null) {
+      fail(message, stackTrace);
+    } else {
+      error(message, stackTrace);
+    }
+  }
+
+  /// Marks the test as having failed.
+  void fail(String messageText, [StackTrace stack]) {
+    if (result != null) {
+      var newMessage = result == PASS
+          ? 'Test failed after initially passing: $messageText'
+          : 'Test failed more than once: $messageText';
+      // TODO(gram): Should we combine the stack with the old one?
+      _complete(ERROR, newMessage, stack);
+    } else {
+      _complete(FAIL, messageText, stack);
+    }
+  }
+
+  /// Marks the test as having had an unexpected error.
+  void error(String messageText, [StackTrace stack]) {
+    _complete(ERROR, messageText, stack);
+  }
+
+  /// Indicates that an asynchronous callback has completed, and marks the test
+  /// as passing if all outstanding callbacks are complete.
+  void markCallbackComplete() {
+    callbackFunctionsOutstanding--;
+    if (callbackFunctionsOutstanding == 0 && !isComplete) pass();
+  }
+
+  String toString() => result != null ? "$description: $result" : description;
+}
diff --git a/lib/src/simple_configuration.dart b/lib/src/simple_configuration.dart
index bc4d7557cc54e961df627defe3ea1c22210d54c9..c95550ce22f7d8aea7b4b72d8b74573ff8ef71d7 100644
--- a/lib/src/simple_configuration.dart
+++ b/lib/src/simple_configuration.dart
@@ -10,13 +10,17 @@ import '../unittest.dart';
 import 'configuration.dart';
 import 'utils.dart';
 
-/// Hooks to configure the unittest library for different platforms. This class
-/// implements the API in a platform-independent way. Tests that want to take
-/// advantage of the platform can create a subclass and override methods from
-/// this class.
+/// A configuration that provides hooks to configure the unittest library for
+/// different platforms.
+///
+/// This class implements the [Configuration] API in a platform-independent way.
+/// Tests that want to take advantage of the platform can create a subclass and
+/// override methods from this class.
 class SimpleConfiguration extends Configuration {
-  // The VM won't shut down if a receive port is open. Use this to make sure
-  // we correctly wait for asynchronous tests.
+  /// A port that keeps the VM alive while we wait for asynchronous tests to
+  /// finish.
+  ///
+  /// The VM won't shut down as long as there's an open receive port.
   ReceivePort _receivePort;
 
   /// If true (the default), throw an exception at the end if any tests failed.
@@ -38,16 +42,11 @@ class SimpleConfiguration extends Configuration {
   /// Called when each test starts. Useful to show intermediate progress on
   /// a test suite. Derived classes should call this first before their own
   /// override code.
-  void onTestStart(TestCase testCase) {
-    assert(testCase != null);
-  }
+  void onTestStart(TestCase testCase) {}
 
-  void onTestResultChanged(TestCase testCase) {
-    assert(testCase != null);
-  }
-
-  /// Handles the logging of messages by a test case. The default in
-  /// this base configuration is to call print();
+  /// Handles the logging of messages by a test case.
+  ///
+  /// The default in this base configuration is to call [print].
   void onLogMessage(TestCase testCase, String message) {
     print(message);
   }
@@ -74,17 +73,17 @@ class SimpleConfiguration extends Configuration {
 
   /// Called with the result of all test cases.
   ///
-  /// The default implementation prints the result summary using the built-in
-  /// [print] command. Browser tests commonly override this to reformat the
-  /// output.
+  /// The default implementation prints the result summary using [print],
+  /// formatted with [formatResult]. Browser tests commonly override this to
+  /// reformat the output.
   ///
   /// When [uncaughtError] is not null, it contains an error that occured
   /// outside of tests (e.g. setting up the test).
   void onSummary(int passed, int failed, int errors, List<TestCase> results,
       String uncaughtError) {
     // Print each test's result.
-    for (final t in results) {
-      print(formatResult(t).trim());
+    for (var test in results) {
+      print(formatResult(test).trim());
     }
 
     // Show the summary.
diff --git a/lib/src/spread_args_helper.dart b/lib/src/spread_args_helper.dart
deleted file mode 100644
index f93e9ba6133e4e569c9fe39f707d0ab374140fc1..0000000000000000000000000000000000000000
--- a/lib/src/spread_args_helper.dart
+++ /dev/null
@@ -1,163 +0,0 @@
-part of unittest;
-
-const _PLACE_HOLDER = const _ArgPlaceHolder();
-
-/// Used to track unused positional args.
-class _ArgPlaceHolder {
-  const _ArgPlaceHolder();
-}
-
-/// Simulates spread arguments using named arguments.
-class _SpreadArgsHelper {
-  final Function callback;
-  final int minExpectedCalls;
-  final int maxExpectedCalls;
-  final Function isDone;
-  final String id;
-  final String reason;
-  int actualCalls = 0;
-  final TestCase testCase;
-  bool complete;
-
-  _SpreadArgsHelper(Function callback, int minExpected, int maxExpected,
-      String id, String reason, {bool isDone()})
-      : this.callback = callback,
-        minExpectedCalls = minExpected,
-        maxExpectedCalls = (maxExpected == 0 && minExpected > 0)
-            ? minExpected
-            : maxExpected,
-        this.isDone = isDone,
-        this.reason = reason == null ? '' : '\n$reason',
-        this.testCase = currentTestCase,
-        this.id = _makeCallbackId(id, callback) {
-    ensureInitialized();
-    if (testCase == null) {
-      throw new StateError("No valid test. Did you forget to run your test "
-          "inside a call to test()?");
-    }
-
-    if (isDone != null || minExpected > 0) {
-      testCase._callbackFunctionsOutstanding++;
-      complete = false;
-    } else {
-      complete = true;
-    }
-  }
-
-  static String _makeCallbackId(String id, Function callback) {
-    // Try to create a reasonable id.
-    if (id != null) {
-      return "$id ";
-    } else {
-      // If the callback is not an anonymous closure, try to get the
-      // name.
-      var fname = callback.toString();
-      var prefix = "Function '";
-      var pos = fname.indexOf(prefix);
-      if (pos > 0) {
-        pos += prefix.length;
-        var epos = fname.indexOf("'", pos);
-        if (epos > 0) {
-          return "${fname.substring(pos, epos)} ";
-        }
-      }
-    }
-    return '';
-  }
-
-  bool shouldCallBack() {
-    ++actualCalls;
-    if (testCase.isComplete) {
-      // Don't run if the test is done. We don't throw here as this is not
-      // the current test, but we do mark the old test as having an error
-      // if it previously passed.
-      if (testCase.result == PASS) {
-        testCase._error('Callback ${id}called ($actualCalls) after test case '
-            '${testCase.description} has already been marked as '
-            '${testCase.result}.$reason');
-      }
-      return false;
-    } else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) {
-      throw new TestFailure('Callback ${id}called more times than expected '
-          '($maxExpectedCalls).$reason');
-    }
-    return true;
-  }
-
-  void after() {
-    if (!complete) {
-      if (minExpectedCalls > 0 && actualCalls < minExpectedCalls) return;
-      if (isDone != null && !isDone()) return;
-
-      // Mark this callback as complete and remove it from the testcase
-      // oustanding callback count; if that hits zero the testcase is done.
-      complete = true;
-      testCase._markCallbackComplete();
-    }
-  }
-
-  /// Returns a function that has as many required + positional arguments as
-  /// [callback] (up to a total of 6).
-  ///
-  /// Optional positional arguments are supported by using const place-holders
-  Function get func {
-    if (callback is _Func6) return _max6;
-    if (callback is _Func5) return _max5;
-    if (callback is _Func4) return _max4;
-    if (callback is _Func3) return _max3;
-    if (callback is _Func2) return _max2;
-    if (callback is _Func1) return _max1;
-    if (callback is _Func0) return _max0;
-
-    throw new ArgumentError(
-        'The callback argument has more than 6 required arguments');
-  }
-
-  /// This indirection is critical. It ensures the returned function has an
-  /// argument count of zero.
-  _max0() => _max6();
-
-  _max1([a0 = _PLACE_HOLDER]) => _max6(a0);
-
-  _max2([a0 = _PLACE_HOLDER, a1 = _PLACE_HOLDER]) => _max6(a0, a1);
-
-  _max3([a0 = _PLACE_HOLDER, a1 = _PLACE_HOLDER, a2 = _PLACE_HOLDER]) =>
-      _max6(a0, a1, a2);
-
-  _max4([a0 = _PLACE_HOLDER, a1 = _PLACE_HOLDER, a2 = _PLACE_HOLDER,
-      a3 = _PLACE_HOLDER]) => _max6(a0, a1, a2, a3);
-
-  _max5([a0 = _PLACE_HOLDER, a1 = _PLACE_HOLDER, a2 = _PLACE_HOLDER,
-      a3 = _PLACE_HOLDER, a4 = _PLACE_HOLDER]) => _max6(a0, a1, a2, a3, a4);
-
-  _max6([a0 = _PLACE_HOLDER, a1 = _PLACE_HOLDER, a2 = _PLACE_HOLDER,
-      a3 = _PLACE_HOLDER, a4 = _PLACE_HOLDER, a5 = _PLACE_HOLDER]) {
-    var args = [a0, a1, a2, a3, a4, a5];
-    args.removeWhere((a) => a == _PLACE_HOLDER);
-
-    return _guardAsync(() {
-      if (shouldCallBack()) {
-        return Function.apply(callback, args);
-      }
-    }, after, testCase);
-  }
-
-  _guardAsync(Function tryBody, Function finallyBody, TestCase testCase) {
-    assert(testCase != null);
-    try {
-      return tryBody();
-    } catch (e, trace) {
-      _registerException(testCase, e, trace);
-    } finally {
-      if (finallyBody != null) finallyBody();
-    }
-  }
-}
-
-typedef _Func0();
-typedef _Func1(a);
-typedef _Func2(a, b);
-typedef _Func3(a, b, c);
-typedef _Func4(a, b, c, d);
-typedef _Func5(a, b, c, d, e);
-typedef _Func6(a, b, c, d, e, f);
diff --git a/lib/src/test_case.dart b/lib/src/test_case.dart
index e6d2171c4c768af7991dc3963d00628387378439..37596ff599c434d206980fc2d7b0d661ace52e6b 100644
--- a/lib/src/test_case.dart
+++ b/lib/src/test_case.dart
@@ -2,189 +2,54 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-part of unittest;
+library unittest.test_case;
 
-/// Represents the state for an individual unit test.
-///
-/// Create by calling [test] or [solo_test].
-class TestCase {
-  /// Identifier for this test.
-  final int id;
+import '../unittest.dart';
 
-  /// A description of what the test is specifying.
-  final String description;
-
-  /// The setup function to call before the test, if any.
-  Function _setUp;
-
-  /// The teardown function to call after the test, if any.
-  Function _tearDown;
+/// An individual unit test.
+abstract class TestCase {
+  /// A unique numeric identifier for this test case.
+  int get id;
 
-  /// The body of the test case.
-  TestFunction _testFunction;
-
-  /// Remaining number of callbacks functions that must reach a 'done' state
-  /// to wait for before the test completes.
-  int _callbackFunctionsOutstanding = 0;
+  /// A description of what the test is specifying.
+  String get description;
 
-  String _message = '';
-  /// Error or failure message.
-  String get message => _message;
+  /// The error or failure message for the tests.
+  ///
+  /// Initially an empty string.
+  String get message;
 
-  String _result;
-  /// One of [PASS], [FAIL], [ERROR], or [:null:] if the test hasn't run yet.
-  String get result => _result;
+  /// The result of the test case.
+  ///
+  /// If the test case has is completed, this will be one of [PASS], [FAIL], or
+  /// [ERROR]. Otherwise, it will be `null`.
+  String get result;
 
   /// Returns whether this test case passed.
-  bool get passed => _result == PASS;
+  bool get passed;
 
-  StackTrace _stackTrace;
-  /// Stack trace associated with this test, or [:null:] if it succeeded.
-  StackTrace get stackTrace => _stackTrace;
+  /// The stack trace for the error that caused this test case to fail, or
+  /// `null` if it succeeded.
+  StackTrace get stackTrace;
 
-  /// The group (or groups) under which this test is running.
-  final String currentGroup;
+  /// The name of the group within which this test is running.
+  String get currentGroup;
 
-  DateTime _startTime;
-  DateTime get startTime => _startTime;
+  /// The time the test case started running.
+  ///
+  /// `null` if the test hasn't yet begun running.
+  DateTime get startTime;
 
-  Duration _runningTime;
-  Duration get runningTime => _runningTime;
+  /// The amount of time the test case took.
+  ///
+  /// `null` if the test hasn't finished running.
+  Duration get runningTime;
 
-  bool _enabled = true;
-
-  bool get enabled => _enabled;
-
-  Completer _testComplete;
-
-  TestCase._internal(this.id, this.description, this._testFunction)
-      : currentGroup = _environment.currentContext.fullName,
-        _setUp = _environment.currentContext.testSetup,
-        _tearDown = _environment.currentContext.testTeardown;
+  /// Whether this test is enabled.
+  ///
+  /// Disabled tests won't be run.
+  bool get enabled;
 
+  /// Whether this test case has finished running.
   bool get isComplete => !enabled || result != null;
-
-  Function _errorHandler(String stage) => (e, stack) {
-    if (stack == null && e is Error) {
-      stack = e.stackTrace;
-    }
-    if (result == null || result == PASS) {
-      if (e is TestFailure) {
-        _fail("$e", stack);
-      } else {
-        _error("$stage failed: Caught $e", stack);
-      }
-    }
-  };
-
-  /// Perform any associated [_setUp] function and run the test. Returns
-  /// a [Future] that can be used to schedule the next test. If the test runs
-  /// to completion synchronously, or is disabled, null is returned, to
-  /// tell unittest to schedule the next test immediately.
-  Future _run() {
-    if (!enabled) return new Future.value();
-
-    _result = _stackTrace = null;
-    _message = '';
-
-    // Avoid calling [new Future] to avoid issue 11911.
-    return new Future.value().then((_) {
-      if (_setUp != null) return _setUp();
-    }).catchError(_errorHandler('Setup')).then((_) {
-      // Skip the test if setup failed.
-      if (result != null) return new Future.value();
-      _config.onTestStart(this);
-      _startTime = new DateTime.now();
-      _runningTime = null;
-      ++_callbackFunctionsOutstanding;
-      var testReturn = _testFunction();
-      // If _testFunction() returned a future, we want to wait for it like we
-      // would a callback, so if a failure occurs while waiting, we can abort.
-      if (testReturn is Future) {
-        ++_callbackFunctionsOutstanding;
-        testReturn
-            .catchError(_errorHandler('Test'))
-            .whenComplete(_markCallbackComplete);
-      }
-    }).catchError(_errorHandler('Test')).then((_) {
-      _markCallbackComplete();
-      if (result == null) {
-        // Outstanding callbacks exist; we need to return a Future.
-        _testComplete = new Completer();
-        return _testComplete.future.whenComplete(() {
-          if (_tearDown != null) {
-            return _tearDown();
-          }
-        }).catchError(_errorHandler('Teardown'));
-      } else if (_tearDown != null) {
-        return _tearDown();
-      }
-    }).catchError(_errorHandler('Teardown')).whenComplete(() {
-      _setUp = null;
-      _tearDown = null;
-      _testFunction = null;
-    });
-  }
-
-  // Set the results, notify the config, and return true if this
-  // is the first time the result is being set.
-  void _setResult(String testResult, String messageText, StackTrace stack) {
-    _message = messageText;
-    _stackTrace = getTrace(stack, formatStacks, filterStacks);
-    if (_stackTrace == null) _stackTrace = stack;
-    if (result == null) {
-      _result = testResult;
-      _config.onTestResult(this);
-    } else {
-      _result = testResult;
-      _config.onTestResultChanged(this);
-    }
-  }
-
-  void _complete(String testResult,
-      [String messageText = '', StackTrace stack]) {
-    if (runningTime == null) {
-      // The startTime can be `null` if an error happened during setup. In this
-      // case we simply report a running time of 0.
-      if (startTime != null) {
-        _runningTime = new DateTime.now().difference(startTime);
-      } else {
-        _runningTime = const Duration(seconds: 0);
-      }
-    }
-    _setResult(testResult, messageText, stack);
-    if (_testComplete != null) {
-      var t = _testComplete;
-      _testComplete = null;
-      t.complete(this);
-    }
-  }
-
-  void _pass() {
-    _complete(PASS);
-  }
-
-  void _fail(String messageText, [StackTrace stack]) {
-    if (result != null) {
-      String newMessage = (result == PASS)
-          ? 'Test failed after initially passing: $messageText'
-          : 'Test failed more than once: $messageText';
-      // TODO(gram): Should we combine the stack with the old one?
-      _complete(ERROR, newMessage, stack);
-    } else {
-      _complete(FAIL, messageText, stack);
-    }
-  }
-
-  void _error(String messageText, [StackTrace stack]) {
-    _complete(ERROR, messageText, stack);
-  }
-
-  void _markCallbackComplete() {
-    if (--_callbackFunctionsOutstanding == 0 && !isComplete) {
-      _pass();
-    }
-  }
-
-  String toString() => _result != null ? "$description: $result" : description;
 }
diff --git a/lib/src/test_environment.dart b/lib/src/test_environment.dart
index ebb39bdc5e4ab087ae00b2a3439754810e49a332..6c205a9f1c9a891b2dfeb1d4eb909d1907280a0d 100644
--- a/lib/src/test_environment.dart
+++ b/lib/src/test_environment.dart
@@ -2,21 +2,44 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-part of unittest;
+library unittest.test_environment;
 
-/// Class for encapsulating test environment state.
+import 'dart:async';
+
+import 'configuration.dart';
+import 'group_context.dart';
+import 'internal_test_case.dart';
+
+/// The default unittest environment.
+final _defaultEnvironment = new TestEnvironment();
+
+/// The current unittest environment.
+TestEnvironment get environment {
+  var environment = Zone.current[#unittest.environment];
+  return environment == null ? _defaultEnvironment : environment;
+}
+
+// The current environment's configuration.
+Configuration get config => environment.config;
+
+/// Encapsulates the state of the test environment.
 ///
 /// This is used by the [withTestEnvironment] method to support multiple
 /// invocations of the unittest library within the same application
 /// instance.
-class _TestEnvironment {
+class TestEnvironment {
+  /// The environment's configuration.
   Configuration config;
 
-  // We use a 'dummy' context for the top level to eliminate null
-  // checks when querying the context. This allows us to easily
-  //  support top-level [setUp]/[tearDown] functions as well.
-  final rootContext = new _GroupContext();
-  _GroupContext currentContext;
+  /// The top-level group context.
+  ///
+  /// We use a 'dummy' context for the top level to eliminate null checks when
+  /// querying the context. This allows us to easily support top-level
+  /// [setUp]/[tearDown] functions as well.
+  final rootContext = new GroupContext.root();
+
+  /// The current group context.
+  GroupContext currentContext;
 
   /// The [currentTestCaseIndex] represents the index of the currently running
   /// test case.
@@ -33,22 +56,19 @@ class _TestEnvironment {
   /// The time since we last gave asynchronous code a chance to be scheduled.
   int lastBreath = new DateTime.now().millisecondsSinceEpoch;
 
-  /// The set of tests to run can be restricted by using [solo_test] and
-  /// [solo_group].
-  ///
-  /// As groups can be nested we use a counter to keep track of the nesting
-  /// level of soloing, and a flag to tell if we have seen any solo tests.
+  /// The number of [solo_group]s deep we are currently.
   int soloNestingLevel = 0;
+
+  /// Whether we've seen a [solo_test].
   bool soloTestSeen = false;
 
   /// The list of test cases to run.
-  final List<TestCase> testCases = new List<TestCase>();
+  final testCases = new List<InternalTestCase>();
 
-  /// The [uncaughtErrorMessage] holds the error messages that are printed
-  /// in the test summary.
+  /// The error message that is printed in the test summary.
   String uncaughtErrorMessage;
 
-  _TestEnvironment() {
+  TestEnvironment() {
     currentContext = rootContext;
   }
 }
diff --git a/lib/unittest.dart b/lib/unittest.dart
index bc1d4dc1ddab2d917cc5bf1efa6654f247c0e704..d0083270f6bfff8beadd318417477e50cb3b3a2b 100644
--- a/lib/unittest.dart
+++ b/lib/unittest.dart
@@ -8,8 +8,11 @@ import 'dart:async';
 import 'dart:collection';
 
 import 'src/configuration.dart';
-import 'src/expect.dart';
-import 'src/utils.dart';
+import 'src/expected_function.dart';
+import 'src/group_context.dart';
+import 'src/internal_test_case.dart';
+import 'src/test_case.dart';
+import 'src/test_environment.dart';
 
 export 'package:matcher/matcher.dart'
     hide
@@ -46,358 +49,339 @@ export 'src/future_matchers.dart';
 export 'src/prints_matcher.dart';
 export 'src/throws_matcher.dart';
 export 'src/throws_matchers.dart';
+export 'src/test_case.dart';
 
-part 'src/group_context.dart';
-part 'src/spread_args_helper.dart';
-part 'src/test_case.dart';
-part 'src/test_environment.dart';
-
-const Symbol _UNITTEST_ENVIRONMENT = #unittest.environment;
-
-final _TestEnvironment _defaultEnvironment = new _TestEnvironment();
-
-/**
- * Internal getter for the current unittest config.
- */
-_TestEnvironment get _environment {
-  var environment = Zone.current[_UNITTEST_ENVIRONMENT];
-  if (environment == null) return _defaultEnvironment;
-  return environment;
-}
-
-// Convenience getter for the current environment's config.
-Configuration get _config => _environment.config;
-
-// Convenience setter for the current environment's config.
-void set _config(Configuration config) {
-  _environment.config = config;
-}
-
-// Convenience getter for the current environment's test cases.
-List<TestCase> get _testCases => _environment.testCases;
+/// The signature for a function passed to [test].
+typedef dynamic TestFunction();
 
 /// [Configuration] used by the unittest library.
 ///
 /// Note that if a configuration has not been set, calling this getter will
 /// create a default configuration.
 Configuration get unittestConfiguration {
-  if (_config == null) {
-    _config = new Configuration();
-  }
-  return _config;
+  if (config == null) environment.config = new Configuration();
+  return config;
 }
 
+/// If `true`, stack traces are reformatted to be more readable.
+bool formatStacks = true;
+
+/// If `true`, irrelevant frames are filtered from the stack trace.
+///
+/// This does nothing if [formatStacks] is false.
+bool filterStacks = true;
+
+/// Separator used between group names and test names.
+String groupSep = ' ';
+
 /// 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) {
-      logMessage('Warning: The unittestConfiguration has already been set. New '
-          'unittestConfiguration ignored.');
-    } else {
-      _config = value;
-    }
+  if (identical(config, value)) return;
+  if (config != null) {
+    logMessage('Warning: The unittestConfiguration has already been set. New '
+        'unittestConfiguration ignored.');
+  } else {
+    environment.config = value;
   }
 }
 
-/// Can be called by tests to log status. Tests should use this
-/// instead of [print].
+/// Logs [message] associated with the current test case.
+///
+/// Tests should use this instead of [print].
 void logMessage(String message) =>
-    _config.onLogMessage(currentTestCase, message);
+    config.onLogMessage(currentTestCase, message);
 
-/// Separator used between group names and test names.
-String groupSep = ' ';
-
-/// Tests executed in this suite.
+/// The test cases that have been defined so far.
 List<TestCase> get testCases =>
-    new UnmodifiableListView<TestCase>(_environment.testCases);
+    new UnmodifiableListView<TestCase>(environment.testCases);
 
-/// Interval (in msecs) after which synchronous tests will insert an async
-/// delay to allow DOM or other updates.
+/// The interval (in milliseconds) after which a non-microtask asynchronous
+/// delay will be scheduled between tests.
+///
+/// This is used to avoid starving the DOM or other non-microtask events.
 const int BREATH_INTERVAL = 200;
 
-/// [TestCase] currently being executed.
-TestCase get currentTestCase => (_environment.currentTestCaseIndex >= 0 &&
-        _environment.currentTestCaseIndex < testCases.length)
-    ? testCases[_environment.currentTestCaseIndex]
+/// The [TestCase] currently being executed.
+TestCase get currentTestCase => (environment.currentTestCaseIndex >= 0 &&
+        environment.currentTestCaseIndex < testCases.length)
+    ? testCases[environment.currentTestCaseIndex]
     : null;
 
-/* Test case result strings. */
-// TODO(gram) we should change these constants to use a different string
-// (so that writing 'FAIL' in the middle of a test doesn't
-// imply that the test fails). We can't do it without also changing
-// the testrunner and test.dart though.
-/// Result string for a passing test case.
+/// The same as [currentTestCase], but typed as an [InternalTestCase].
+InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase;
+
+/// The result string for a passing test case.
 const PASS = 'pass';
-/// Result string for a failing test case.
+
+/// The result string for a failing test case.
 const FAIL = 'fail';
-/// Result string for an test case with an error.
+
+/// The result string for an test case with an error.
 const ERROR = 'error';
 
-/// 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) {
+/// Creates a new test case with the given description and body.
+///
+/// The description will be added to the descriptions of any surrounding
+/// [group]s.
+void test(String description, TestFunction body) {
   _requireNotRunning();
   ensureInitialized();
-  if (!_environment.soloTestSeen || _environment.soloNestingLevel > 0) {
-    var testcase =
-        new TestCase._internal(testCases.length + 1, _fullSpec(spec), body);
-    _testCases.add(testcase);
-  }
+
+  if (environment.soloTestSeen && environment.soloNestingLevel == 0) return;
+  var testCase = new InternalTestCase(
+      testCases.length + 1, _fullDescription(description), body);
+  environment.testCases.add(testCase);
 }
 
-/// Convenience function for skipping a test.
+/// Returns [description] with all of its group prefixes prepended.
+String _fullDescription(String description) {
+  var group = environment.currentContext.fullName;
+  if (description == null) return group;
+  return group != '' ? '$group$groupSep$description' : description;
+}
+
+/// A convenience function for skipping a test.
 void skip_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.
-///
-/// If we use [solo_test] (or [solo_group]) instead of test, then all non-solo
-/// tests will be disabled. Note that if we use [solo_group], all tests in
-/// the group will be enabled, regardless of whether they use [test] or
-/// [solo_test], or whether they are in a nested [group] vs [solo_group]. Put
-/// another way, if there are any calls to [solo_test] or [solo_group] in a test
-/// file, all tests that are not inside a [solo_group] will be disabled unless
-/// they are [solo_test]s.
+/// Creates a new test case with the given description and body.
 ///
-/// [skip_test] and [skip_group] take precedence over soloing, by virtue of the
-/// fact that they are effectively no-ops.
+/// If [solo_test] is used instead of [test], then all non-solo tests will be
+/// disabled. Note that if [solo_group] is used as well, all tests in the group
+/// will be enabled, regardless of whether they use [test] or [solo_test], or
+/// whether they are in a nested [group] versus [solo_group]. Put another way,
+/// if there are any calls to [solo_test] or [solo_group] in a test file, all
+/// tests that are not inside a [solo_group] will be disabled unless they are
+/// [solo_test]s.
 void solo_test(String spec, TestFunction body) {
   _requireNotRunning();
   ensureInitialized();
-  if (!_environment.soloTestSeen) {
-    _environment.soloTestSeen = true;
+  if (!environment.soloTestSeen) {
+    environment.soloTestSeen = true;
     // This is the first solo-ed test. Discard all tests up to now.
-    _testCases.clear();
+    environment.testCases.clear();
   }
-  ++_environment.soloNestingLevel;
+  environment.soloNestingLevel++;
   try {
     test(spec, body);
   } finally {
-    --_environment.soloNestingLevel;
+    environment.soloNestingLevel--;
   }
 }
 
-/// Indicate that [callback] is expected to be called a [count] number of times
+/// Indicate that [callback] is expected to be called [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
-/// [expectAsync] 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.
+/// The unittest framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete. Using [expectAsync]
+/// will also ensure that errors that occur within [callback] are tracked and
+/// reported. [callback] may take up to six optional or required positional
+/// arguments; named arguments are not supported.
 ///
-/// [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.
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
 ///
-/// [reason] is optional and is typically not supplied, as a reason is generated
-/// by the unittest package; if reason is included it is appended to the
-/// generated reason.
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
 Function expectAsync(Function callback,
     {int count: 1, int max: 0, String id, String reason}) =>
-        new _SpreadArgsHelper(callback, count, max, id, reason).func;
+        new ExpectedFunction(callback, count, max, id: id, reason: reason).func;
 
 /// Indicate that [callback] is expected to be called until [isDone] returns
 /// true.
 ///
-/// The unittest framework checks [isDone] after each callback and only
-/// when it returns true will it continue with the following test. Using
-/// [expectAsyncUntil] 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).
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete. Using [expectAsyncUntil] will
+/// also ensure that errors that occur within [callback] are tracked and
+/// reported. [callback] may take up to six optional or required positional
+/// arguments; named arguments are not supported.
 ///
-/// [reason] is optional and is typically not supplied, as a reason is generated
-/// by the unittest package; if reason is included it is appended to the
-/// generated reason.
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
 Function expectAsyncUntil(Function callback, bool isDone(),
-    {String id, String reason}) =>
-        new _SpreadArgsHelper(callback, 0, -1, id, reason, isDone: isDone).func;
+    {String id, String reason}) => new ExpectedFunction(callback, 0, -1,
+        id: id, reason: reason, isDone: isDone).func;
 
-/// Creates a new named group of tests.
+/// Creates a group of tests.
 ///
-/// Calls to group() or test() within the body of the function passed to this
-/// named group will inherit this group's description.
+/// A group's description is included in the descriptions of any tests or
+/// sub-groups it contains. [setUp] and [tearDown] are also scoped to the
+/// containing group.
 void group(String description, void body()) {
   ensureInitialized();
   _requireNotRunning();
-  _environment.currentContext =
-      new _GroupContext(_environment.currentContext, description);
+  environment.currentContext =
+      new GroupContext(environment.currentContext, description);
   try {
     body();
   } catch (e, trace) {
     var stack = (trace == null) ? '' : ': ${trace.toString()}';
-    _environment.uncaughtErrorMessage = "${e.toString()}$stack";
+    environment.uncaughtErrorMessage = "${e.toString()}$stack";
   } finally {
     // Now that the group is over, restore the previous one.
-    _environment.currentContext = _environment.currentContext.parent;
+    environment.currentContext = environment.currentContext.parent;
   }
 }
 
-/// Like [skip_test], but for groups.
+/// A convenience function for skipping a group of tests.
 void skip_group(String description, void body()) {}
 
-/// Like [solo_test], but for groups.
+/// Creates a group of tests.
+///
+/// If [solo_group] is used instead of [group], then all tests not declared with
+/// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a
+/// [solo_group] will be run, regardless of whether they're declared with [test]
+/// or [solo_test].
+///
+/// [skip_test] and [skip_group] take precedence over [solo_group].
 void solo_group(String description, void body()) {
   _requireNotRunning();
   ensureInitialized();
-  if (!_environment.soloTestSeen) {
-    _environment.soloTestSeen = true;
+  if (!environment.soloTestSeen) {
+    environment.soloTestSeen = true;
     // This is the first solo-ed group. Discard all tests up to now.
-    _testCases.clear();
+    environment.testCases.clear();
   }
-  ++_environment.soloNestingLevel;
+  ++environment.soloNestingLevel;
   try {
     group(description, body);
   } finally {
-    --_environment.soloNestingLevel;
+    --environment.soloNestingLevel;
   }
 }
 
-/// Register a [setUp] function for a test [group].
+/// Registers a function to be run before tests.
 ///
-/// This function will be called before each test in the group is 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) {
+/// This function will be called before each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run after any set-up callbacks in parent groups or
+/// at the top level.
+void setUp(Function callback) {
   _requireNotRunning();
-  _environment.currentContext.testSetup = setupTest;
+  environment.currentContext.testSetUp = callback;
 }
 
-/// Register a [tearDown] function for a test [group].
+/// Registers a function to be run after tests.
 ///
-/// This function will be called after each test in the group is run.
+/// This function will be called after each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
 ///
-/// 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) {
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run before any tear-down callbacks in parent groups or
+/// at the top level.
+void tearDown(Function callback) {
   _requireNotRunning();
-  _environment.currentContext.testTeardown = teardownTest;
+  environment.currentContext.testTearDown = callback;
 }
 
 /// Advance to the next test case.
 void _nextTestCase() {
-  _environment.currentTestCaseIndex++;
+  environment.currentTestCaseIndex++;
   _runTest();
 }
 
-/// Handle errors that happen outside the tests.
-// TODO(vsm): figure out how to expose the stack trace here
-// Currently e.message works in dartium, but not in dartc.
-void handleExternalError(e, String message, [stack]) {
+/// Handle an error that occurs outside of any test.
+void handleExternalError(e, String message, [stackTrace]) {
   var msg = '$message\nCaught $e';
 
   if (currentTestCase != null) {
-    currentTestCase._error(msg, stack);
+    _currentTestCase.error(msg, stackTrace);
   } else {
-    _environment.uncaughtErrorMessage = "$msg: $stack";
+    environment.uncaughtErrorMessage = "$msg: $stackTrace";
   }
 }
 
-/// Filter the tests by [testFilter].
+/// Remove any tests that match [testFilter].
+///
+/// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's
+/// a function, it's called with each [TestCase]. If it's a [String], it's
+/// parsed as a [RegExp] and matched against each [TestCase.description].
 ///
-/// [testFilter] can be a [RegExp], a [String] or a
-/// predicate function. This is different from enabling or disabling tests
-/// in that it removes the tests completely.
+/// This is different from enabling or disabling tests in that it removes the
+/// tests completely.
 void filterTests(testFilter) {
   var filterFunction;
   if (testFilter is String) {
-    RegExp re = new RegExp(testFilter);
+    var 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);
+  environment.testCases.retainWhere(filterFunction);
 }
 
 /// Runs all queued tests, one at a time.
 void runTests() {
   _requireNotRunning();
   _ensureInitialized(false);
-  _environment.currentTestCaseIndex = 0;
-  _config.onStart();
+  environment.currentTestCaseIndex = 0;
+  config.onStart();
   _runTest();
 }
 
-/// Registers that an exception was caught for the current test.
-void registerException(e, [trace]) {
-  _registerException(currentTestCase, e, trace);
-}
-
-/// Registers that an exception was caught for the current test.
-void _registerException(TestCase testCase, e, [trace]) {
-  String message = (e is TestFailure) ? e.message : 'Caught $e';
-  if (testCase.result == null) {
-    testCase._fail(message, trace);
-  } else {
-    testCase._error(message, trace);
-  }
-}
+/// Registers an exception that was caught for the current test.
+void registerException(error, [StackTrace stackTrace]) =>
+    _currentTestCase.registerException(error, stackTrace);
 
 /// Runs the next test.
 void _runTest() {
-  if (_environment.currentTestCaseIndex >= testCases.length) {
-    assert(_environment.currentTestCaseIndex == testCases.length);
+  if (environment.currentTestCaseIndex >= testCases.length) {
+    assert(environment.currentTestCaseIndex == testCases.length);
     _completeTests();
-  } else {
-    var testCase = testCases[_environment.currentTestCaseIndex];
-    Future f = runZoned(testCase._run, onError: (error, stack) {
-      // TODO(kevmoo) Do a better job of flagging these are async errors.
-      // https://code.google.com/p/dart/issues/detail?id=16530
-      _registerException(testCase, error, stack);
-    });
-
-    var timeout = unittestConfiguration.timeout;
-
-    Timer timer;
-    if (timeout != null) {
-      try {
-        timer = new Timer(timeout, () {
-          testCase._error("Test timed out after ${timeout.inSeconds} seconds.");
-          _nextTestCase();
-        });
-      } on UnsupportedError catch (e) {
-        if (e.message != "Timer greater than 0.") rethrow;
-        // Support running on d8 and jsshell which don't support timers.
-      }
+    return;
+  }
+
+  var testCase = _currentTestCase;
+  var f = runZoned(testCase.run, onError: (error, stack) {
+    // TODO(kevmoo) Do a better job of flagging these are async errors.
+    // https://code.google.com/p/dart/issues/detail?id=16530
+    testCase.registerException(error, stack);
+  });
+
+  var timer;
+  var timeout = unittestConfiguration.timeout;
+  if (timeout != null) {
+    try {
+      timer = new Timer(timeout, () {
+        testCase.error("Test timed out after ${timeout.inSeconds} seconds.");
+        _nextTestCase();
+      });
+    } on UnsupportedError catch (e) {
+      if (e.message != "Timer greater than 0.") rethrow;
+      // Support running on d8 and jsshell which don't support timers.
     }
-    f.whenComplete(() {
-      if (timer != null) timer.cancel();
-      var now = new DateTime.now().millisecondsSinceEpoch;
-      if ((now - _environment.lastBreath) >= BREATH_INTERVAL) {
-        _environment.lastBreath = now;
-        Timer.run(_nextTestCase);
-      } else {
-        scheduleMicrotask(_nextTestCase); // Schedule the next test.
-      }
-    });
   }
+
+  f.whenComplete(() {
+    if (timer != null) timer.cancel();
+    var now = new DateTime.now().millisecondsSinceEpoch;
+    if (now - environment.lastBreath >= BREATH_INTERVAL) {
+      environment.lastBreath = now;
+      Timer.run(_nextTestCase);
+    } else {
+      scheduleMicrotask(_nextTestCase); // Schedule the next test.
+    }
+  });
 }
 
-/// Publish results on the page and notify controller.
+/// Notify the configuration that the testing has finished.
 void _completeTests() {
-  if (!_environment.initialized) return;
-  int passed = 0;
-  int failed = 0;
-  int errors = 0;
+  if (!environment.initialized) return;
 
-  for (TestCase t in testCases) {
-    switch (t.result) {
+  var passed = 0;
+  var failed = 0;
+  var errors = 0;
+  for (var testCase in testCases) {
+    switch (testCase.result) {
       case PASS:
         passed++;
         break;
@@ -409,97 +393,77 @@ void _completeTests() {
         break;
     }
   }
-  _config.onSummary(
-      passed, failed, errors, testCases, _environment.uncaughtErrorMessage);
-  _config.onDone(passed > 0 &&
+
+  config.onSummary(
+      passed, failed, errors, testCases, environment.uncaughtErrorMessage);
+  config.onDone(passed > 0 &&
       failed == 0 &&
       errors == 0 &&
-      _environment.uncaughtErrorMessage == null);
-  _environment.initialized = false;
-  _environment.currentTestCaseIndex = -1;
-}
-
-String _fullSpec(String spec) {
-  var group = '${_environment.currentContext.fullName}';
-  if (spec == null) return group;
-  return group != '' ? '$group$groupSep$spec' : spec;
+      environment.uncaughtErrorMessage == null);
+  environment.initialized = false;
+  environment.currentTestCaseIndex = -1;
 }
 
-/// Lazily initializes the test library if not already initialized.
+/// Initializes the test environment if it hasn't already been initialized.
 void ensureInitialized() {
   _ensureInitialized(true);
 }
 
+/// Initializes the test environment.
+///
+/// If [configAutoStart] is `true`, schedule a microtask to run the tests. This
+/// microtask is expected to run after all the tests are defined.
 void _ensureInitialized(bool configAutoStart) {
-  if (_environment.initialized) {
-    return;
-  }
-  _environment.initialized = true;
+  if (environment.initialized) return;
+
+  environment.initialized = true;
 
-  _environment.uncaughtErrorMessage = null;
+  environment.uncaughtErrorMessage = null;
 
   unittestConfiguration.onInit();
 
-  if (configAutoStart && _config.autoStart) {
-    // Immediately queue the suite up. It will run after a timeout (i.e. after
-    // main() has returned).
-    scheduleMicrotask(runTests);
-  }
+  // Immediately queue the suite up. It will run after a timeout (i.e. after
+  // main() has returned).
+  if (configAutoStart && config.autoStart) scheduleMicrotask(runTests);
 }
 
-/// Select a solo test by ID.
-void setSoloTest(int id) => _testCases.retainWhere((t) => t.id == id);
+/// Remove all tests other than the one identified by [id].
+void setSoloTest(int id) =>
+    environment.testCases.retainWhere((t) => t.id == id);
+
+/// Enable the test identified by [id].
+void enableTest(int id) => _setTestEnabledState(id, enable: true);
 
-/// Enable/disable a test by ID.
-void _setTestEnabledState(int testId, bool state) {
+/// Disable the test by [id].
+void disableTest(int id) => _setTestEnabledState(id, enable: false);
+
+/// Enable or disable the test identified by [id].
+void _setTestEnabledState(int id, {bool enable: true}) {
   // Try fast path first.
-  if (testCases.length > testId && testCases[testId].id == testId) {
-    testCases[testId]._enabled = state;
+  if (testCases.length > id && testCases[id].id == id) {
+    environment.testCases[id].enabled = enable;
   } else {
     for (var i = 0; i < testCases.length; i++) {
-      if (testCases[i].id == testId) {
-        testCases[i]._enabled = state;
-        break;
-      }
+      if (testCases[i].id != id) continue;
+      environment.testCases[i].enabled = enable;
+      break;
     }
   }
 }
 
-/// Enable a test by ID.
-void enableTest(int testId) => _setTestEnabledState(testId, true);
-
-/// Disable a test by ID.
-void disableTest(int testId) => _setTestEnabledState(testId, false);
-
-/// Signature for a test function.
-typedef dynamic TestFunction();
-
-/// A flag that controls whether we hide unittest and core library details in
-/// exception stacks.
-///
-/// Useful to disable when debugging unittest or matcher customizations.
-bool formatStacks = true;
-
-/// A flag that controls whether we try to filter out irrelevant frames from
-/// the stack trace.
-///
-/// Requires [formatStacks] to be set.
-bool filterStacks = true;
-
+/// Throws a [StateError] if tests are running.
 void _requireNotRunning() {
-  if (_environment.currentTestCaseIndex != -1) {
-    throw new StateError('Not allowed when tests are running.');
-  }
+  if (environment.currentTestCaseIndex == -1) return;
+  throw new StateError('Not allowed when tests are running.');
 }
 
-/// Method to create a test environment running in its own zone scope.
+/// Creates a test environment running in its own zone scope.
 ///
 /// This allows for multiple invocations of the unittest library in the same
-/// application instance.
-/// This is useful when, for example, creating a test runner application which
-/// needs to create a new pristine test environment on each invocation to run
-/// a given set of test.
-dynamic withTestEnvironment(callback()) {
+/// application instance. This is useful when, for example, creating a test
+/// runner application which needs to create a new pristine test environment on
+/// each invocation to run a given set of tests.
+withTestEnvironment(callback()) {
   return runZoned(callback,
-      zoneValues: {_UNITTEST_ENVIRONMENT: new _TestEnvironment()});
+      zoneValues: {#unittest.environment: new TestEnvironment()});
 }
diff --git a/test/late_exception_test.dart b/test/late_exception_test.dart
index 14d14fe742e2492a3023526bae18fedc05241ea3..94ad30b3fdd3f832c322b45ff410c0e9b43b27d3 100644
--- a/test/late_exception_test.dart
+++ b/test/late_exception_test.dart
@@ -28,7 +28,7 @@ void _test(message) {
   }, [
     {
       'description': 'testOne',
-      'message': 'Callback called (2) after test case testOne has already been '
+      'message': 'Callback called (2) after test case testOne had already been '
           'marked as pass.',
       'result': 'error',
     },