Unit Testing (test() / expect() / group())
The dart:test package provides a standard testing framework for writing and running unit tests in Dart. You define test cases with the test() function and write assertions with expect(). Running dart test executes your tests, letting you develop with confidence.
Syntax
// Import the package
import 'package:test/test.dart';
// test(): defines a single test case
test('test name', () {
// Write the test body here
expect(actualValue, matcher);
});
// group(): groups related tests together
group('group name', () {
setUp(() {
// Initialization code that runs before each test
});
tearDown(() {
// Cleanup code that runs after each test
});
test('test name', () {
expect(actualValue, matcher);
});
});
// Commonly used matchers
expect(value, equals(expected)); // Check for equality
expect(value, isTrue); // Check that value is true
expect(value, isFalse); // Check that value is false
expect(value, isNull); // Check that value is null
expect(value, isNotNull); // Check that value is not null
expect(value, isA<Type>()); // Check the type
expect(value, contains('string')); // Check that a string or collection contains an element
expect(value, hasLength(n)); // Check the length
expect(value, greaterThan(n)); // Check that value is greater than n
expect(value, lessThan(n)); // Check that value is less than n
expect(value, throwsA(isA<Exception>())); // Check that an exception is thrown
expect(() => fn(), throwsException); // Check that any exception is thrown
Syntax Reference
| Function / Matcher | Description |
|---|---|
test(name, body) | Defines a single test case. Pass a string describing the test as name and the test logic as body. |
group(name, body) | Groups related tests together. The group name is prepended to each test name in the output. |
setUp(fn) | Registers a setup function that runs before each test. Use it to initialize state for every test. |
tearDown(fn) | Registers a cleanup function that runs after each test. Use it to release resources. |
setUpAll(fn) | Runs once before all tests in a group. Use it for expensive initialization that only needs to happen once. |
tearDownAll(fn) | Runs once after all tests in a group have completed. |
expect(actual, matcher) | Verifies that the actual value satisfies the matcher condition. The test fails if the condition is not met. |
equals(expected) | A matcher that verifies the value is equal to expected. |
isTrue / isFalse | Matchers that verify the value is true or false. |
isNull / isNotNull | Matchers that verify the value is null or not null. |
isA<T>() | A matcher that verifies the value is an instance of type T. |
contains(value) | A matcher that verifies a string or collection contains the specified element. |
hasLength(n) | A matcher that verifies a string or collection has a length of n. |
greaterThan(n) / lessThan(n) | Matchers that verify the value is greater than or less than n. |
throwsA(matcher) | A matcher that verifies a function throws an exception matching the given matcher. |
throwsException | A matcher that verifies a function throws any Exception. |
throwsArgumentError | A matcher that verifies a function throws an ArgumentError. |
Sample Code
jjk_sorcerer_test.dart
// jjk_sorcerer_test.dart — sample demonstrating basic dart:test usage
// Uses a sorcerer class from Jujutsu Kaisen as the subject under test
import 'package:test/test.dart';
// -----------------------------------------------
// Class definitions for the subject under test
// -----------------------------------------------
// Enum representing a sorcerer's grade
enum SorcererGrade { special, first, second, third, fourth }
// Sorcerer class
class Sorcerer {
final String name;
final SorcererGrade grade;
final String technique;
int cursedEnergy; // Mutable value that changes over time
Sorcerer({
required this.name,
required this.grade,
required this.technique,
required this.cursedEnergy,
});
// Activates a technique. Consumes cursed energy and returns a result string.
String activate(int cost) {
if (cost <= 0) {
throw ArgumentError('Activation cost must be 1 or greater');
}
if (cursedEnergy < cost) {
throw StateError('Not enough cursed energy (required: $cost, remaining: $cursedEnergy)');
}
cursedEnergy -= cost;
return '${name} activated ${technique} (remaining energy: $cursedEnergy)';
}
// Restores cursed energy
void recover(int amount) {
if (amount <= 0) {
throw ArgumentError('Recovery amount must be 1 or greater');
}
cursedEnergy += amount;
}
// Returns true if the sorcerer is special grade
bool get isSpecialGrade => grade == SorcererGrade.special;
// Returns a string representation of the sorcerer
@override
String toString() => '$name (${grade.name}): $technique';
}
// Sorcerer team class
class SorcererTeam {
final String teamName;
final List<Sorcerer> members;
SorcererTeam({required this.teamName, required this.members});
// Returns the total cursed energy of all members
int get totalCursedEnergy =>
members.fold(0, (sum, s) => sum + s.cursedEnergy);
// Returns a list of special-grade members
List<Sorcerer> get specialGradeMembers =>
members.where((s) => s.isSpecialGrade).toList();
// Finds a member by name. Returns null if not found.
Sorcerer? findByName(String name) {
try {
return members.firstWhere((s) => s.name == name);
} catch (_) {
return null;
}
}
}
// -----------------------------------------------
// Test code
// -----------------------------------------------
void main() {
// -----------------------------------------------
// Tests for the Sorcerer class
// -----------------------------------------------
group('Sorcerer class', () {
// Initialize sorcerer instances before each test
late Sorcerer gojo;
late Sorcerer itadori;
late Sorcerer nobara;
setUp(() {
// setUp runs before each test, ensuring a fresh state every time
gojo = Sorcerer(name: '五条悟', grade: SorcererGrade.special, technique: '無下限呪術', cursedEnergy: 9999);
itadori = Sorcerer(name: '虎杖悠仁', grade: SorcererGrade.first, technique: '発勁', cursedEnergy: 500);
nobara = Sorcerer(name: '釘崎野薔薇', grade: SorcererGrade.third, technique: '芻霊呪法', cursedEnergy: 300);
});
// --- Test using the equals matcher ---
test('name property is set correctly', () {
expect(gojo.name, equals('五条悟'));
expect(itadori.name, equals('虎杖悠仁'));
expect(nobara.name, equals('釘崎野薔薇'));
});
// --- Test using the isTrue / isFalse matchers ---
test('isSpecialGrade returns the correct value', () {
expect(gojo.isSpecialGrade, isTrue); // 五条悟 is special grade
expect(itadori.isSpecialGrade, isFalse); // 虎杖悠仁 is not special grade
});
// --- Test for state changes ---
test('activate() consumes cursed energy correctly', () {
var result = itadori.activate(100);
// Check remaining energy after consumption
expect(itadori.cursedEnergy, equals(400));
// Check that the return value contains the sorcerer name and technique
expect(result, contains('虎杖悠仁'));
expect(result, contains('発勁'));
});
test('recover() restores cursed energy correctly', () {
nobara.recover(200);
expect(nobara.cursedEnergy, equals(500));
});
// --- Test using greaterThan / lessThan matchers ---
test('五条悟 has more cursed energy than 虎杖悠仁', () {
expect(gojo.cursedEnergy, greaterThan(itadori.cursedEnergy));
expect(nobara.cursedEnergy, lessThan(itadori.cursedEnergy));
});
// --- Exception tests ---
group('activate() exception tests', () {
test('throws ArgumentError when cost is 0 or less', () {
// Use throwsA and isA to check the type of the thrown exception
expect(
() => itadori.activate(0),
throwsA(isA<ArgumentError>()),
);
});
test('throws StateError when cursed energy is insufficient', () {
expect(
() => nobara.activate(9999), // Only has 300 energy but requests 9999
throwsA(isA<StateError>()),
);
});
test('recover() throws ArgumentError when amount is 0 or less', () {
// throwsArgumentError is a convenient shorthand matcher
expect(() => gojo.recover(-1), throwsArgumentError);
});
});
// --- Test for toString ---
test('toString() returns the correct format', () {
var str = gojo.toString();
// Use isA to check the type
expect(str, isA<String>());
expect(str, contains('五条悟'));
expect(str, contains('無下限呪術'));
});
});
// -----------------------------------------------
// Tests for the SorcererTeam class
// -----------------------------------------------
group('SorcererTeam class', () {
late SorcererTeam team;
// setUpAll runs once for the entire group
setUpAll(() {
print('Setting up SorcererTeam tests');
});
setUp(() {
// Initialize the team before each test
team = SorcererTeam(
teamName: 'Tokyo School Selection Team',
members: [
Sorcerer(name: '五条悟', grade: SorcererGrade.special, technique: '無下限呪術', cursedEnergy: 9999),
Sorcerer(name: '虎杖悠仁', grade: SorcererGrade.first, technique: '発勁', cursedEnergy: 500),
Sorcerer(name: '釘崎野薔薇', grade: SorcererGrade.third, technique: '芻霊呪法', cursedEnergy: 300),
Sorcerer(name: '伏黒恵', grade: SorcererGrade.first, technique: '十種影法術', cursedEnergy: 600),
],
);
});
tearDownAll(() {
print('SorcererTeam tests complete');
});
// --- Test using the hasLength matcher ---
test('team has 4 members', () {
expect(team.members, hasLength(4));
});
// --- Test using isNotNull / isNull matchers ---
test('findByName() finds an existing member', () {
var found = team.findByName('伏黒恵');
expect(found, isNotNull);
expect(found!.technique, equals('十種影法術'));
});
test('findByName() returns null for a non-existent member', () {
var notFound = team.findByName('夏油傑');
expect(notFound, isNull);
});
// --- Tests for collection matchers ---
test('specialGradeMembers contains only 五条悟', () {
var specials = team.specialGradeMembers;
expect(specials, hasLength(1));
expect(specials.first.name, equals('五条悟'));
});
test('totalCursedEnergy matches the sum of all members', () {
// 9999 + 500 + 300 + 600 = 11399
expect(team.totalCursedEnergy, equals(11399));
});
});
}
dart test jjk_sorcerer_test.dart Building package executable... Built test:test. 00:02 +12: All tests passed!
Overview
Dart's testing framework is provided by package:test/test.dart. Use test() to define individual test cases and group() to organize related tests. setUp() and tearDown() run initialization and cleanup code around each test, while setUpAll() and tearDownAll() run once for the entire group. Write assertions using expect() combined with matchers such as equals, isTrue, isNull, isA<T>(), contains, and throwsA. Run your tests with dart test filename.dart; all passing tests produce the message All tests passed!. For details on exception handling, see Exception Handling (try / catch / throw). For asynchronous testing, see async / await.
If you find any errors or copyright issues, please contact us.