diff --git a/src/site/articles/dart-unit-tests/index.markdown b/src/site/articles/dart-unit-tests/index.markdown index 7055d31ce..8bd7ee7cb 100644 --- a/src/site/articles/dart-unit-tests/index.markdown +++ b/src/site/articles/dart-unit-tests/index.markdown @@ -12,7 +12,10 @@ _Written by Graham Wheeler
June 2012_ -Dart includes a unit test library that is easy to use, adaptable, and supports both synchronous and asynchronous tests. This article describes how to write and run tests, how testing fits into the overall Dart ecosystem, and what is coming next. +Dart includes a unit test library that is easy to use, adaptable, and supports +both synchronous and asynchronous tests. This article describes how to write and +run tests, how testing fits into the overall Dart ecosystem, and what is coming +next. ## Contents @@ -30,12 +33,18 @@ Dart includes a unit test library that is easy to use, adaptable, and supports b ## Differences from the earlier library -The unit test library is not new, but has undergone some changes. If you have been using the library or the Expect class in dart:core, here are the main changes to bear in mind: +The unit test library is not new, but has undergone some changes. If you have +been using the library or the Expect class in dart:core, here are the main +changes to bear in mind: -* asyncTest is deprecated and will soon go away; instead write your async tests with `test()` and use expectAsync and/or guardAsync for callbacks -* the syntax of `expect()` has changed, and a large set of matchers is now available for use with `expect()` -* expectThrows is deprecated and will soon go away, instead use the throws or `throwsA()` matchers with `expect()` -* the Expect class in dart:core is deprecated except for internal use, and `expect()` should be used instead +* asyncTest is deprecated and will soon go away; instead write your async tests + with `test()` and use expectAsync and/or guardAsync for callbacks +* the syntax of `expect()` has changed, and a large set of matchers is now + available for use with `expect()` +* expectThrows is deprecated and will soon go away, instead use the throws or + `throwsA()` matchers with `expect()` +* the Expect class in dart:core is deprecated except for internal use, and + `expect()` should be used instead The new `expect()` is modelled on 3rd generation assert libraries like [Hamcrest](http://code.google.com/p/hamcrest/), and borrows some ideas @@ -44,7 +53,10 @@ library. ## A simple unit test example -For those who want to jump right to it, let's start with an example. Let's say we are writing a quicksort algorithm in Dart and want to test it. Open the Dart editor, create a new application "quicksort", then change the "quicksort.dart" file to look as follows: +For those who want to jump right to it, let's start with an example. Let's say +we are writing a quicksort algorithm in Dart and want to test it. Open the Dart +editor, create a new application "quicksort", then change the "quicksort.dart" +file to look as follows: #import('package:unittest/unittest.dart'); @@ -87,7 +99,10 @@ For those who want to jump right to it, let's start with an example. Let's say w ); } -There are a couple of problems with this code that we will use unit tests to discover. To begin we have just one test in `main()` which asserts that a 5-element array is properly sorted after calling QuickSort. Note how the test is written: we wrap it in a call to `test()` with this form: +There are a couple of problems with this code that we will use unit tests to +discover. To begin we have just one test in `main()` which asserts that a +5-element array is properly sorted after calling QuickSort. Note how the test is +written: we wrap it in a call to `test()` with this form: test(String testName, functionToTest); @@ -95,7 +110,10 @@ Within the function we are testing, we write assertions, using `expect()`: expect(actualValue, expectedValueMatcher); -The second argument to expect is what we call a "matcher". It can be a scalar value, or a special matcher function, of which the library provides many; in our case we are using `orderedEquals()` which matches against an Iterable object in index order. +The second argument to expect is what we call a "matcher". It can be a scalar +value, or a special matcher function, of which the library provides many; in our +case we are using `orderedEquals()` which matches against an Iterable object in +index order. When we run this application, we see output similar to this: @@ -117,13 +135,16 @@ When we run this application, we see output similar to this: 4. Function: '::function' url: 'http://127.0.0.1:34677/home/gram/svn/dart/lib/unittest/unittest.dart' line:482 col:13 5. Function: '_ReceivePortImpl@6be832b._handleMessage@6be832b' url: 'dart:isolate' line:19 col:46 -This has two parts - the first part prints the results for each test as it is run, and the second part is a summary of the whole test run, which repeats the information for the failed tests. +This has two parts - the first part prints the results for each test as it is +run, and the second part is a summary of the whole test run, which repeats the +information for the failed tests. The problem here was this line: int pivotIndex = left + ((right-left) / 2); -The right hand side is a double, which we can't assign to int. We can fix this by changing the line to this: +The right hand side is a double, which we can't assign to int. We can fix this +by changing the line to this: int pivotIndex = left + ((right-left) / 2).toInt(); @@ -135,9 +156,16 @@ Running the test again, we see: (We are omitting the stack traces and summary details here for brevity). -This tells us that we have a bug, but doesn't help us find it. We need to dig deeper. +This tells us that we have a bug, but doesn't help us find it. We need to dig +deeper. -The partition part of quicksort is given a pivot index (which in turn maps to a pivot value), and is meant to move all values less than the pivot value to the left of the pivot, and all values greater to the right of the pivot, and then return the final position of the pivot value. So if we passed [3,2,1] and pivot index 1 (i.e. pivot value 2), after partitioning we should have [1,2,3] and the returned pivot index should still be 1. Let's test that, by adding a second test. Change `main()` by adding the code in bold below: +The partition part of quicksort is given a pivot index (which in turn maps to a +pivot value), and is meant to move all values less than the pivot value to the +left of the pivot, and all values greater to the right of the pivot, and then +return the final position of the pivot value. So if we passed [3,2,1] and pivot +index 1 (i.e. pivot value 2), after partitioning we should have [1,2,3] and the +returned pivot index should still be 1. Let's test that, by adding a second +test. Change `main()` by adding the code in bold below: void main() { test('QuickSort', () => @@ -158,9 +186,16 @@ If we run this again, we still see the first test fail, but now we also see: Expected: <1> but: was <0> -So there is a problem with partition. We did not get to the second `expect()`; the first one failed. If we look carefully at the code, we can see that _Partition returns the final value of storeIndex, which is initialized to the value of left. Look more carefully, and we can see that storeIndex doesn't have its value changed after initialization - which explains why we got a returned index of 0 after passing in 0 for left. Clearly we missed something here. The hazards of writing algorithms from memory! Off to Wikipedia... +So there is a problem with partition. We did not get to the second `expect()`; +the first one failed. If we look carefully at the code, we can see that +_Partition returns the final value of storeIndex, which is initialized to the +value of left. Look more carefully, and we can see that storeIndex doesn't have +its value changed after initialization - which explains why we got a returned +index of 0 after passing in 0 for left. Clearly we missed something here. The +hazards of writing algorithms from memory! Off to Wikipedia... -A bit of research shows the problem. The loop in _Partition is meant to increment storeIndex: +A bit of research shows the problem. The loop in _Partition is meant to +increment storeIndex: for (var i = left; i < right; i++) { if (array[i] < pivotValue) { @@ -187,13 +222,15 @@ and add a line before the tests: useVmConfiguration(); -This will result in a zero exit code if all tests pass or a 1 exit code upon test failure. -See [Configuring the test environment](#configuring-the-test-environment) later for more details. +This will result in a zero exit code if all tests pass or a 1 exit code upon +test failure. +See [Configuring the test environment](#configuring-the-test-environment) later +for more details. You can run the test with the command: dart Quicksort.dart -If you prefer the DartEditor environment add this import: +If you prefer the DartEditor environment, add this import: #import('package:unittest/html_config.dart'); @@ -201,13 +238,18 @@ and add a line before the tests: useHtmlConfiguration(); -Test results will be displayed in the Dartium window and exit codes are shown in the DartEditor Debugger tab. +Test results will be displayed in the Dartium window and exit codes are shown in +the DartEditor Debugger tab. The rest of this article dives deeper into the unit test library. ## Basic synchronous tests -Tests are created using the top level function `test()`. This function takes a name for the test and a function to execute. Typically the calls to `test()` would be in a `main()` function or in a function called from `main()`. In contrast to some languages where reflection is used to determine unit test functions, in Dart they must be explicitly called. +Tests are created using the top level function `test()`. This function takes a +name for the test and a function to execute. Typically the calls to `test()` +would be in a `main()` function or in a function called from `main()`. +In contrast to some languages where reflection is used to determine unit test +functions, in Dart they must be explicitly called. Here is a trivial example to illustrate the syntax of `test()`: @@ -218,15 +260,23 @@ Here is a trivial example to illustrate the syntax of `test()`: }); } -This test doesn't do anything useful and will always pass. Note that test functions takes no arguments and return no value; if a value is returned it is ignored. +This test doesn't do anything useful and will always pass. Note that test +functions takes no arguments and return no value; if a value is returned it is +ignored. -Of course, a real test will have some content in the body of the test function, and that body will usually be making assertions about the state of the system under test. To express such assertions we use `expect()`. `expect()` is typically called with two arguments: an actual value and a "matcher" that tests if the value satisfies some constraint. For example: +Of course, a real test will have some content in the body of the test function, +and that body will usually be making assertions about the state of the system +under test. To express such assertions we use `expect()`. `expect()` is +typically called with two arguments: an actual value and a "matcher" that tests +if the value satisfies some constraint. For example: test('Addition test', () { expect(2 + 2, equals(4)); }); -If the matcher fails then an ExpectException is thrown, which will be caught and handled by the unit test framework as a test failure. Later we will see how this behavior of `expect()` can be customized to use it in other contexts. +If the matcher fails then an ExpectException is thrown, which will be caught and +handled by the unit test framework as a test failure. Later we will see how this +behavior of `expect()` can be customized to use it in other contexts. It is possible to simply pass a predicate to `expect()`, as in: @@ -234,7 +284,10 @@ It is possible to simply pass a predicate to `expect()`, as in: expect(2 + 2 == 5); }); -However, if you use matchers, `expect()` generates a useful descriptive message upon failure, which is passed as an argument to the ExpectException constructor. When using the predicate form, `expect()` has no useful information to do this with. So in the second case, the description will simply be: +However, if you use matchers, `expect()` generates a useful descriptive message +upon failure, which is passed as an argument to the ExpectException constructor. +When using the predicate form, `expect()` has no useful information to do this +with. So in the second case, the description will simply be: Assertion failed @@ -243,7 +296,11 @@ while in the first case it is the more descriptive: Expected: a value equal to 5 But: was <4> -It is possible to pass an additional string argument to `expect()` (using either form) which will be appended to the output, and doing so is strongly encouraged if using the predicate form to improve the resulting output. When using the predicate form this argument must be named `reason`; when using matchers it can simply be the third positional argument. For example: +It is possible to pass an additional string argument to `expect()` (using either +form) which will be appended to the output, and doing so is strongly encouraged +if using the predicate form to improve the resulting output. When using the +predicate form this argument must be named `reason`; when using matchers it can +simply be the third positional argument. For example: test('Addition test', () => expect(2 + 2 == 5, reason:'Two twos are not five')); @@ -252,11 +309,14 @@ which results in: Assertion failed Two twos are not five -There are circumstances when the predicate form is preferable to using matchers. If you have code that should be unreachable, then you can use: +There are circumstances when the predicate form is preferable to using matchers. +If you have code that should be unreachable, then you can use: expect(false, reason:'Unreachable'); -Another case might be where you have a complex predicate that would be too tedious to write using composite matchers, or where the predicate is implemented by a function, and where a simpler text description is useful. For example: +Another case might be where you have a complex predicate that would be too +tedious to write using composite matchers, or where the predicate is implemented +by a function, and where a simpler text description is useful. For example: expect(isPrime(x), reason:'${x} is not prime'); @@ -266,27 +326,40 @@ The latter example could be written as: However in this case using the isTrue matcher is not adding any value. -There are a large set of possible matchers that can be used with `expect()`, and it is possible to create custom ones. In fact, matchers can be composed to create more complex matchers. Matchers will be discussed in more detail later in this article. +There are a large set of possible matchers that can be used with `expect()`, and +it is possible to create custom ones. In fact, matchers can be composed to +create more complex matchers. Matchers will be discussed in more detail later in +this article. -Note that `test()` calls cannot be nested; each call to `test()` defines one and only one test. +Note that `test()` calls cannot be nested; each call to `test()` defines one and +only one test. ## Grouping tests -It can be helpful to group similar tests together, which can be done with `group()`. The `group()` function has a similar form to `test()`, taking a name and a function as arguments, with the function containing the tests in the group. For example: +It can be helpful to group similar tests together, which can be done with +`group()`. The `group()` function has a similar form to `test()`, taking a name +and a function as arguments, with the function containing the tests in the +group. For example: group('My test group', () { test('Test 1', () => expect(0, equals(1)); test('Test 2', () => expect(1, equals(0)); }); -Test names have their group names prepended; in this case, the first test has the name 'My test group Test 1' and the second test has the name 'My test group Test 2'. Test groups can be nested (with multiple group names prepended). +Test names have their group names prepended; in this case, the first test has +the name 'My test group Test 1' and the second test has the name 'My test group +Test 2'. Test groups can be nested (with multiple group names prepended). -Groups are not themselves tests, and so should not include assertions or calls to expect() outside of the contained tests. +Groups are not themselves tests, and so should not include assertions or calls +to expect() outside of the contained tests. ## Setup and teardown -Inside a group body, in addition to calling test() you can call setUp() and/or tearDown() with function arguments. The function passed as an argument to setUp() will be called before each test, and that passed to tearDown() will be called after each test. +Inside a group body, in addition to calling test() you can call setUp() and/or +tearDown() with function arguments. The function passed as an argument to +setUp() will be called before each test, and that passed to tearDown() will be +called after each test. Usually you would set these up at the start of the group: @@ -297,34 +370,56 @@ Usually you would set these up at the start of the group: ... }); -However you can interlace them differently; each test() will use the most recently set values. +However you can interlace them differently; each test() will use the most +recently set values. -Whenever a new group is started these functions are reset. This applies for nested groups too. Nested groups do not inherit these functions or augment them; they get their own. This is the most flexible approach as chaining can always be done explicitly. +Whenever a new group is started these functions are reset. This applies for +nested groups too. Nested groups do not inherit these functions or augment them; +they get their own. This is the most flexible approach as chaining can always be +done explicitly. ## Running a limited set of tests -The Dart unittest library provides a mechanism to quickly run just one unit test. This is useful if you have a failing test that you want to explore in the debugger, and you want to remove the distraction of other tests. To isolate a test, change the name of the call for that test from `test()` to `solo_test()`. If there is one `solo_test()` call then only that test will be run (if you mistakenly have more than one `solo_test()` then an exception will be thrown). +The Dart unittest library provides a mechanism to quickly run just one unit test. +This is useful if you have a failing test that you want to explore in the +debugger, and you want to remove the distraction of other tests. To isolate a +test, change the name of the call for that test from `test()` to `solo_test()`. +If there is one `solo_test()` call then only that test will be run (if you +mistakenly have more than one `solo_test()` then an exception will be thrown). -Another way of reducing the set of tests which are run is to use the `setFilter(pattern)` function. This function takes a `String` argument that is used to create a `RegExp`, which in turn is matched against each test description; only those tests that match will be run. +Another way of reducing the set of tests which are run is to use the +`setFilter(pattern)` function. This function takes a `String` argument that is +used to create a `RegExp`, which in turn is matched against each test +description; only those tests that match will be run. ## Asynchronous tests -So far all of the tests shown have been synchronous; that is, the test function body runs to completion and when it returns the test is done. What about testing asynchronous code? We need a way to tell the test framework that it should not consider a test complete until one or more callbacks have been run a certain number of times. +So far all of the tests shown have been synchronous; that is, the test function +body runs to completion and when it returns the test is done. What about testing +asynchronous code? We need a way to tell the test framework that it should not +consider a test complete until one or more callbacks have been run a certain +number of times. Say we have this code in our application: window.setTimeout(checkProgress, 100); -and we want to test whether window.setTimeout calls the checkProgress function. If we just write: +and we want to test whether window.setTimeout calls the checkProgress function. +If we just write: test('Window timeout test', () { window.setTimeout(checkProgress, 100); }); -the test will pass, but in fact is not doing what we want. There is no assertion or other check to tell that checkProgress was ever fired. We need to have the test understand that it is testing asynchronous code, and then either succeed if the callback is executed or fail after some time has elapsed and nothing has happened. +the test will pass, but in fact is not doing what we want. There is no assertion +or other check to tell that checkProgress was ever fired. We need to have the +test understand that it is testing asynchronous code, and then either succeed if +the callback is executed or fail after some time has elapsed and nothing has +happened. -The unit test library provides expectAsyncN (where N is the number of arguments) for asynchronous tests. +The unit test library provides expectAsyncN (where N is the number of arguments) +for asynchronous tests. Here is an example of expectAsync0: @@ -332,9 +427,14 @@ Here is an example of expectAsync0: window.setTimeout(expectAsync0(checkProgress), 100); }); -When this test starts to run, it calls `window.setTimeout` and passes a closure, created by `expectAsync0`, as the event handler. This closure will in turn call `checkProgress()`. If `checkProgress()` throws an exception the closure catches it and mark the test as failed. The test is not considered complete until either the closure is executed or the test framework times out and fails the test. +When this test starts to run, it calls `window.setTimeout` and passes a closure, +created by `expectAsync0`, as the event handler. This closure will in turn call +`checkProgress()`. If `checkProgress()` throws an exception the closure catches +it and mark the test as failed. The test is not considered complete until either +the closure is executed or the test framework times out and fails the test. -`expectAsyncN()` can take an additional count argument to specify how many times the callback must be called before the test is considered complete. For example: +`expectAsyncN()` can take an additional count argument to specify how many times +the callback must be called before the test is considered complete. For example: test('Double callback', () { var callback = expectAsync0(foo, count: 2); @@ -342,7 +442,12 @@ When this test starts to run, it calls `window.setTimeout` and passes a closure, window.setTimeout(callback, 0); }); -There are times when we have callbacks that we don't expect to be called. For example, consider a function that takes two callback arguments, and only one of them will be called (a common example would be onSuccess and onFailure handlers). Even though we don't expect some callback to be called, we still need to guard the code so that the test harness handles the failure case where it is called. We can do this with `expectAsyncN()` with a count parameter of zero. For example: +There are times when we have callbacks that we don't expect to be called. For +example, consider a function that takes two callback arguments, and only one of +them will be called (a common example would be onSuccess and onFailure handlers). +Even though we don't expect some callback to be called, we still need to guard +the code so that the test harness handles the failure case where it is called. +We can do this with `expectAsyncN()` with a count parameter of zero. For example: test('getDirectory', () { fs.root.getDirectory('nonexistent', flags:{}, @@ -355,13 +460,21 @@ There are times when we have callbacks that we don't expect to be called. For ex }); We might have a callback that's called an undetermined number of times, where -only a test can tell us when it's the last time. For these cases we can use `expectAsyncUntilN()` (where N is 0, 1 or 2). These functions take a second function argument which should return false if more callbacks are expected or true if all callbacks are done. +only a test can tell us when it's the last time. For these cases we can use +`expectAsyncUntilN()` (where N is 0, 1 or 2). These functions take a second +function argument which should return false if more callbacks are expected or +true if all callbacks are done. ## Matchers -So far we have only looked at the `equals(v)` matcher. The Dart unittest library contains a large set of predefined matchers, which we will look at briefly now. The Dart SDK documentation contains details for each matcher. Note that a number of matchers can in turn take matchers as their arguments; in these cases simple values can be used too, and they will automatically be wrapped in `equals(v)` matchers. For example: +So far we have only looked at the `equals(v)` matcher. The Dart unittest library +contains a large set of predefined matchers, which we will look at briefly now. +The Dart SDK documentation contains details for each matcher. Note that a number +of matchers can in turn take matchers as their arguments; in these cases simple +values can be used too, and they will automatically be wrapped in `equals(v)` +matchers. For example: expect(foo, hasLength(6)); @@ -369,7 +482,8 @@ is turned into: expect(foo, hasLength(equals(6)); -The following simple matchers take no arguments, and have mostly self-evident meanings: +The following simple matchers take no arguments, and have mostly self-evident +meanings: isNull isNotNull @@ -412,7 +526,8 @@ For string matching, we have: stringContainsInOrder(List substrings) matches(regexp) -`equalsIgnoringWhitespace(v)` normalizes whitespace runs to single spaces first and trims off leading and trailing whitespace. +`equalsIgnoringWhitespace(v)` normalizes whitespace runs to single spaces first +and trims off leading and trailing whitespace. For objects that have a length property, we have this matcher: @@ -426,19 +541,24 @@ For testing whether functions throw exceptions, we have: throwsA(m) returnsNormally -`throwsA` takes a matcher argument which will be matched against the exception; an example is shown in the next paragraph. `returnsNormally` will swallow any thrown exception and throw an `ExpectException` instead with details of the inner exception including the stack trace. +`throwsA` takes a matcher argument which will be matched against the exception; +an example is shown in the next paragraph. `returnsNormally` will swallow any +thrown exception and throw an `ExpectException` instead with details of the +inner exception including the stack trace. For type checking, we have: new isInstanceOf() -As types are not first class objects in Dart, this is a bit of a kludge but does the job. For example: +As types are not first class objects in Dart, this is a bit of a kludge but does +the job. For example: test('Exception type', expect(()=> throw 'X', throwsA(new isInstanceOf())); -As the usual case is to throw an exception, there are predefined matchers for a number of the core exception types: +As the usual case is to throw an exception, there are predefined matchers for a +number of the core exception types: throwsBadNumberFormatException throwsException @@ -463,11 +583,14 @@ So for example we can write: -For matching the inner content of compound objects, we have a number of matchers, starting with the ubiquitous `equals()`: +For matching the inner content of compound objects, we have a number of matchers, +starting with the ubiquitous `equals()`: equals(object, [depth]) -This works with scalars, Maps and iterables (which should match in order). The depth parameter is to deal with cyclic structures; after [depth] comparisons the match will fail if it has not already terminated. The default depth is 100. +This works with scalars, Maps and iterables (which should match in order). The +depth parameter is to deal with cyclic structures; after [depth] comparisons the +match will fail if it has not already terminated. The default depth is 100. Here is an example, taken from the JSON parse tests: @@ -480,7 +603,9 @@ For testing just a subpart of an object, we can use: contains(m) -This works with Strings (matches substrings), Maps (matches if the Map has that key) or Collections (matches if some element in the collection is a match). In the latter case m can be a matcher, e.g.: +This works with Strings (matches substrings), Maps (matches if the Map has that +key) or Collections (matches if some element in the collection is a match). In +the latter case m can be a matcher, e.g.: expect([1, 2, 3, 4], contains(isNonZero)); @@ -511,11 +636,14 @@ Finally, we have some operators for combining or inverting matchers: allOf(List matchers) anyOf(List matchers) -The `allOf()` and `anyOf()` represent AND/OR operations. They can take a list of matchers or several individual matcher or scalar arguments (limited to 7 in the latter case). +The `allOf()` and `anyOf()` represent AND/OR operations. They can take a list of +matchers or several individual matcher or scalar arguments (limited to 7 in the +latter case). ### Creating custom matchers -Should the set of matchers provided by default be insufficient, it is possible to create your own. A matcher implements the `Matcher` interface: +Should the set of matchers provided by default be insufficient, it is possible +to create your own. A matcher implements the `Matcher` interface: interface Matcher { /** This does the matching of the actual vs expected values. */ @@ -526,7 +654,9 @@ Should the set of matchers provided by default be insufficient, it is possible t Description describeMismatch(item, Description mismatchDescription); } -In most cases rather than extending this interface you would implement a subclass of `BaseMatcher`, to reduce the number of necessary methods that must be implemented from 3 to 2: +In most cases rather than extending this interface you would implement a +subclass of `BaseMatcher`, to reduce the number of necessary methods that must +be implemented from 3 to 2: class BaseMatcher implements Matcher { const BaseMatcher(); @@ -536,7 +666,8 @@ In most cases rather than extending this interface you would implement a subclas mismatchDescription.add('was ').addDescriptionOf(item); } -Here is an example of a custom matcher that matches string prefixes while ignoring whitespace runs: +Here is an example of a custom matcher that matches string prefixes while +ignoring whitespace runs: class PrefixMatcher extends BaseMatcher { final String _prefix; @@ -555,8 +686,10 @@ Here is an example of a custom matcher that matches string prefixes while ignori There are three important parts to this: -* the constructor which needs to take in any expected value information or a matcher that is used to test the expected value; -* the `matches(item)` method which matches an actual value, and returns true if the match is good and false otherwise; +* the constructor which needs to take in any expected value information or a + matcher that is used to test the expected value; +* the `matches(item)` method which matches an actual value, and returns true if + the match is good and false otherwise; * the `describe()` method, which generates a textual description of the matcher. Recall a typical error message from `expect()` looks like: @@ -564,42 +697,76 @@ Recall a typical error message from `expect()` looks like: Expected: a value equal to 5 But: was <4> -The `describe()` method of the matcher is used to build the "Expected:" part of the error message, while the "But:" part is generated by the `describeMismatch()` method, for which BaseMatcher has a default implementation that is good enough for most cases. +The `describe()` method of the matcher is used to build the "Expected:" part of +the error message, while the "But:" part is generated by the `describeMismatch()` +method, for which BaseMatcher has a default implementation that is good enough +for most cases. -Both `describe()` and `describeMismatch()` use the Description class, which has the following useful methods: +Both `describe()` and `describeMismatch()` use the Description class, which has +the following useful methods: * `add(text)` which appends the text to the description; -* `addDescriptionOf(value)` which describes a value, possibly recursively calling `describe()` if the value is a matcher; -* `addAll(start, separator, end, list)` which appends the contents of list (an `Iterable`), formatting it with the provided start, end and separator characters. +* `addDescriptionOf(value)` which describes a value, possibly recursively calling + `describe()` if the value is a matcher; +* `addAll(start, separator, end, list)` which appends the contents of list (an + `Iterable`), formatting it with the provided start, end and separator characters. ## Configuring the test environment -Depending on whether you are running tests from the command line, within the editor, or from a browser, you may want to change how the output is generated (print versus HTML markup, for example). To do this you need to configure the test environment, which you do by calling `configure()`. `configure()` takes a Configuration object argument; a Configuration has several functions that get called at different times during the test process: +Depending on whether you are running tests from the command line, within the +editor, or from a browser, you may want to change how the output is generated +(print versus HTML markup, for example). To do this you need to configure the +test environment, which you do by calling `configure()`. `configure()` takes a +Configuration object argument; a Configuration has several functions that get +called at different times during the test process: -* `onInit()` which is called when the test framework is initialized, before any tests are added; +* `onInit()` which is called when the test framework is initialized, before any + tests are added; * `onStart()` which is called before the first test is run; * `onTestResult(TestCase)` which is called upon completion of each test; -* `onDone(passed, failed, errors, List results, String uncaughtError)` which is called when all tests are done; in the default configuration this prints the test summary. +* `onDone(passed, failed, errors, List results, String uncaughtError)` + which is called when all tests are done; in the default configuration this + prints the test summary. -You don't need to create your own `Configuration` classes; the library has several built-in which should be adequate for most purposes. These are: +You don't need to create your own `Configuration` classes; the library has +several built-in which should be adequate for most purposes. These are: * the default `Configuration`, which prints test results to standard output; -* `VmConfiguration`, which exits the process with a return value of 1 upon failure; useful in particular for driving tests from other programs or scripts where the return code of the process is useful to detect success or failure; call `useVmConfiguration()` to use this, and import vm_config.dart; -* `HtmlConfiguration`, which puts test results in an HTML table and sets the browser document body to be this table; call `useHtmlConfiguration()` to use this, and import html_config.dart; -* `HtmlEnhancedConfiguration`, which is similar to `HtmlConfiguration` but provides a richer layout; call `useHtmlEnhancedConfiguration()` to use this, and import html_enhanced_config.dart. - -For running tests in a continuous integration environment, the default or VmConfigurations are most useful. +* `VmConfiguration`, which exits the process with a return value of 1 upon + failure; useful in particular for driving tests from other programs or scripts + where the return code of the process is useful to detect success or failure; + call `useVmConfiguration()` to use this, and import vm_config.dart; +* `HtmlConfiguration`, which puts test results in an HTML table and sets the + browser document body to be this table; call `useHtmlConfiguration()` to use + this, and import html_config.dart; +* `HtmlEnhancedConfiguration`, which is similar to `HtmlConfiguration` but + provides a richer layout; call `useHtmlEnhancedConfiguration()` to use this, + and import html_enhanced_config.dart. + +For running tests in a continuous integration environment, the default or +VmConfigurations are most useful. ## Using `expect()` in other contexts -While `expect()` has been used here in the context of unit tests, it is possible to use it in other contexts as a general assertion mechanism. The default behavior is to throw an `ExpectException` upon failure with the failure reason as the message property, but that can be customized. In fact, expect has its own unit tests that do just this. +While `expect()` has been used here in the context of unit tests, it is possible +to use it in other contexts as a general assertion mechanism. The default +behavior is to throw an `ExpectException` upon failure with the failure reason +as the message property, but that can be customized. In fact, expect has its own +unit tests that do just this. There are two functions that can be used to customize the behavior of expect: -* `configureExpectHandler` - this is used to change the object which handles `expect()` failures. The default object simply throws ExpectExceptions. It may be desirable to do something different; for example to log the error and swallow the exception instead. -* `configureExpectFormatter` - this is used to change the function which is used to format error messages by `expect()`. It is rare that this would need to change and it will not be considered further here; see the SDK documentation for details. - -The easiest way to customize the error handler is to create a class that inherits from `DefaultFailureHandler` and overrides this method: +* `configureExpectHandler` - this is used to change the object which handles + `expect()` failures. The default object simply throws ExpectExceptions. It may + be desirable to do something different; for example to log the error and + swallow the exception instead. +* `configureExpectFormatter` - this is used to change the function which is used + to format error messages by `expect()`. It is rare that this would need to + change and it will not be considered further here; see the SDK documentation + for details. + +The easiest way to customize the error handler is to create a class that +inherits from `DefaultFailureHandler` and overrides this method: void fail(String reason) { throw new ExpectException(reason);