Holy Cow, Dart Unit Tests are Easy
Dart provides a rich unit test library out of the box. Inspired by testing libraries like Hamcrest and dartmatch, the dart:unittest library provides test groups, numerous matchers, and more. Read on to learn how easy it is to write your first Dart unit tests!
You, too, can create tests this pretty. |
Test subject
To illustrate, I've just created the soon-to-be massively critical embiggen library. This library will bigify your text. It's very advanced.
Of course, I made a pub package for this library:
The contents of embiggen.dart are:
library embiggen;
String embiggen(String msg) {
if (msg == null) {
throw new ArgumentError("must not be null");
}
return msg.toUpperCase();
}
I'll let that sink in for a while, it's pretty intense.
Add the unit test package
You can use pub to get the unit test package and link it into your project. Simply declare the unittest dependency in your pubspec.yaml:
name: embiggen
description: Turns small things into big things.
dependencies:
unittest: any
Dart Editor will automatically run "pub install" when it detects a change to pubspec.yaml. If you are using the command-line tools, you can run "pub install" manually. This step will find the unittest package in your local pub cache, or it will download it from pub.dartlang.org. Once found, pub will set up the proper symlinks to make unittest available to your project.
Set up the tests
The pub package layout convention dictates that tests should be placed inside a "test" directory. Create a new file named "embiggen_test.dart" and place it inside "test".
Import both the embiggen library and the unittest library. You need to import embiggen because your test needs to access the actual embiggen function.
Here is embiggen_test.dart so far:
library embiggen_test;
import 'package:embiggen/embiggen.dart';
import 'package:test/test.dart';
(Note: you may need to run "pub install" again, which will configure symlinks in your "test" directory.)
Write a test
Dart is a language without much ceremony, so let's dive into it. Create a main() function and place the first test inside. A test takes a name (or, a very short description) and an anonymous function that itself contains the test condition.
Here is embiggen_test.dart with a simple test:
library embiggen_test;
import 'package:embiggen/embiggen.dart';
import 'package:test/test.dart';
main() {
test("bigifies text", () {
expect(embiggen("hello"), equals("HELLO"));
});
}
Use "expect(actualValue, expectedValueMatcher)" to perform the actual test. The "expectedValueMatcher" is dynamic, it can be a simple value or a matcher that can test for a wide variety of values. In the above example, equals("HELLO") returns a simple matcher that matches on equality. More on matchers shortly.
Run the test
Choosing to place the tests inside main() was deliberate, it enables the Dart file to act as its own test runner. Simply run the embiggen_test.dart file, either from the command line or via Dart Editor.
Here is the output on the console:
unittest-suite-wait-for-done
PASS: bigifies text
All 1 tests passed.
unittest-suite-success
Out of the box formatting!
A failed test looks like this:
unittest-suite-wait-for-done
FAIL: bigifies text
Expected: 'HELL'
but: was 'HELLO'.
#0 DefaultFailureHandler.fail (package:unittest/src/expect.dart:86:5)
#1 DefaultFailureHandler.failMatch (package:unittest/src/expect.dart:90:9)
#2 expect (package:unittest/src/expect.dart:55:29)
#3 main.<anonymous closure> (file:///Users/sethladd/dart/embiggen/test/embiggen_test.dart:8:11)
#4 TestCase.run (package:unittest/src/test_case.dart:83:11)
#5 _nextBatch._nextBatch.<anonymous closure> (package:unittest/unittest.dart:808:19)
#6 guardAsync (package:unittest/unittest.dart:767:19)
0 PASSED, 1 FAILED, 0 ERRORS
Test for exception
Passing null to embiggen() will throw an ArgumentError, so let's not forget to test for the error condition. First, be sure to wrap the condition to test inside an anonymous function. This ensures that any exceptions thrown by the condition are properly caught. There are plenty of pre-built matchers to help with exceptions, from the simple "throws", which tests that anything was thrown, to specific matchers like "throwsArgumentError".
Here is embiggen_test.dart with a test for an exception:
library embiggen_test;
import 'package:embiggen/embiggen.dart';
import 'package:test/test.dart';
main() {
test("bigifies text", () {
expect(embiggen("hello"), equals("HELLO"));
});
test("null throws ArgumentError", () {
expect(() => embiggen(null), throwsArgumentError);
});
}
What's next
This post barely scratches the surface. Dart's unit test library has support for setup/teardown, test groups, nested groups, a very extensive matchers library, and even mocks/stubs. Dart also has various test outputs, ranging from VM to HTML (which you can see an example of in the top of this post).
Further reading
- Unit testing with Dart article
- Writing unit tests for pub packages in Dart blog post
- Unit test, Part 1 screencast
Summary
Unit tests come out of the box with the Dart SDK. They are simple to write, compose well, and have many matchers. Simply create a "test" directory, add Dart test scripts, and BOOM you're making ice cream.
(Nerd cow credit: http://www.flickr.com/photos/toastersarts/6893702709/)