Watch the video (12 min)
On this page25 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is a Unit Test?
  5. 2. Arrange, Act, Assert
  6. 3. Matchers and equals
  7. 4. isTrue, isFalse, isNull, isNotNull
  8. 5. greaterThan and lessThan
  9. 6. closeTo for Doubles
  10. 7. Strings and Lists
  11. 8. isA<T>() — Checking the Type
  12. 9. reason: — Saying Why
  13. 10. group() and Test Names That Read as Sentences
  14. 11. Reading a Failure Report
  15. 12. Running a Single Test or Group
  16. 13. A Real-World Example
  17. Code Examples
  18. Common Beginner Mistakes
  19. Best Practices
  20. Mini Challenge
  21. Practice Questions
  22. Practice Answers
  23. Quick Revision
  24. Key Takeaways
  25. What's Next?

Lecture 119 — Unit Testing

Category20 — Testing
Lecture119 of 140
Estimated video length12 min
TargetDart 3.12.2
LevelAdvanced

expect(result, 240) can only ask "is it exactly this?". Real checks are richer — close to 0.3, contains 'Sara', has 3 items. Matchers ask those questions, and group() turns a pile of tests into a readable list of facts.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every output in this lecture is real. Each test was run with dart test on Dart 3.12.2, including the failures, so you will recognize them when you meet them yourself.


Prerequisites

Students should already understand, from Lectures 001–118:

  • 0.1 + 0.2 printing 0.30000000000000004 (Lecture 009) and int.tryParse (Lecture 010)
  • functions, arrow functions and anonymous functions (Lectures 030–034)
  • lists, where and maps (Lectures 039–050)
  • lib/, test/ and package: imports (Lecture 103)
  • test(), expect() and running dart test (Lecture 118)

Connection With Previous Lectures

In Lecture 118 every test had the same shape: compute something, then expect(actual, expected). That works while the answer is one exact value. Now try a price:

dart
expect(0.1 + 0.2, 0.3);

This test fails, even though the code is right. And how do you check that a greeting mentions Sara, or that a list has three items? You need more ways to describe "correct" — and a way to organize many tests so a failure tells you instantly what broke.


Learning Objectives

By the end of this lecture you will be able to:

  • Explain what a unit test is
  • Structure a test with Arrange, Act, Assert
  • Use matchers: equals, isTrue, isFalse, isNull, isNotNull, greaterThan, lessThan
  • Compare doubles safely with closeTo
  • Check strings and lists with contains, startsWith, hasLength, isEmpty, isA<T>()
  • Add a reason: and group tests with group()
  • Run a single test or group with --name and --plain-name
  • Read a failure report line by line

1. What Is a Unit Test?

A unit is the smallest piece of code that does one job — usually one function. A unit test checks that one piece on its own, with fixed inputs and a known answer.

Analogy — a car factory tests every brake, light and engine separately before the car is assembled. If a brake fails its test, you know exactly which part to fix.

We use the marks_app project from Lecture 103, with lib/marks_app.dart replaced by:

dart
int total(List<int> marks) {
  var sum = 0;
  for (var mark in marks) {
    sum += mark;
  }
  return sum;
}

double average(List<int> marks) => total(marks) / marks.length;

String grade(int mark) {
  if (mark >= 80) return 'A';
  if (mark >= 60) return 'B';
  return 'C';
}

2. Arrange, Act, Assert

Almost every unit test has three parts, often separated by blank lines:

dart
import 'package:marks_app/marks_app.dart';
import 'package:test/test.dart';

void main() {
  test('total adds every mark', () {
    // Arrange
    var marks = [80, 75, 85];

    // Act
    var result = total(marks);

    // Assert
    expect(result, 240);
  });
}
output
$ dart test test/aaa_test.dart
00:00 +0: loading test/aaa_test.dart
00:00 +0: total adds every mark
00:00 +1: All tests passed!

Arrange prepares the inputs, Act calls the code once, Assert checks the result. (In a terminal, these lines overwrite each other, so you mostly see the last one.)


3. Matchers and equals

The second argument of expect is a matcher: an object that describes what a correct value looks like. A plain value is shorthand for equals:

dart
import 'package:test/test.dart';

void main() {
  test('both lines check the same thing', () {
    expect(2 + 3, 5);
    expect(2 + 3, equals(5));
    expect([1, 2], equals([1, 2]));
  });
}
output
00:00 +0: loading test/equals_test.dart
00:00 +0: both lines check the same thing
00:00 +1: All tests passed!

equals compares lists item by item, while plain [1, 2] == [1, 2] is false — they are two separate lists.


4. isTrue, isFalse, isNull, isNotNull

For bool and nullable results, these read best:

dart
import 'package:test/test.dart';

void main() {
  test('true and false', () {
    var mark = 72;
    expect(mark >= 50, isTrue);
    expect(mark >= 90, isFalse);
  });

  test('null and not null', () {
    var marks = {'Ali': 80, 'Sara': 92};
    expect(marks['Sara'], isNotNull);
    expect(marks['Zayan'], isNull);
  });
}
output
00:00 +0: loading test/bool_null_test.dart
00:00 +0: true and false
00:00 +1: null and not null
00:00 +2: All tests passed!

5. greaterThan and lessThan

Sometimes any value in a range is correct:

dart
import 'package:test/test.dart';

void main() {
  test('comparing numbers', () {
    var temperature = 31.5;
    expect(temperature, greaterThan(30));
    expect(temperature, lessThan(40));
    expect(temperature, greaterThanOrEqualTo(31.5));
  });
}
output
00:00 +0: loading test/compare_test.dart
00:00 +0: comparing numbers
00:00 +1: All tests passed!

6. closeTo for Doubles

Here is the test from the start of the lecture, and its real result:

dart
import 'package:test/test.dart';

void main() {
  test('0.1 + 0.2 is 0.3', () {
    expect(0.1 + 0.2, 0.3);
  });
}
output
00:00 +0: loading test/double_test.dart
00:00 +0: 0.1 + 0.2 is 0.3
00:00 +0 -1: 0.1 + 0.2 is 0.3 [E]
  Expected: <0.3>
    Actual: <0.30000000000000004>

(The rest of the report is explained in section 11.) That is the binary rounding from Lecture 009. closeTo(value, delta) accepts anything within delta of value:

dart
import 'package:test/test.dart';

void main() {
  test('0.1 + 0.2 is close to 0.3', () {
    expect(0.1 + 0.2, closeTo(0.3, 0.0001));
  });
}
output
00:00 +0: loading test/double2_test.dart
00:00 +0: 0.1 + 0.2 is close to 0.3
00:00 +1: All tests passed!

Rule: when a double comes from a calculation, test it with closeTo.


7. Strings and Lists

These matchers check part of a value:

dart
import 'package:test/test.dart';

void main() {
  test('checking text', () {
    var message = 'Hello, Sara! Your total is 240.';
    expect(message, startsWith('Hello'));
    expect(message, endsWith('240.'));
    expect(message, contains('Sara'));
  });

  test('checking a list', () {
    var students = ['Ali', 'Sara', 'Zayan'];
    expect(students, hasLength(3));
    expect(students, contains('Sara'));
    expect(students, isNotEmpty);
    expect(<String>[], isEmpty);
  });
}
output
00:00 +0: loading test/text_list_test.dart
00:00 +0: checking text
00:00 +1: checking a list
00:00 +2: All tests passed!

contains works on both strings and lists.


8. isA<T>() — Checking the Type

isA<T>() passes when the value is a T, like is from Lecture 010:

dart
import 'package:test/test.dart';

void main() {
  test('checking a type', () {
    var average = 240 / 3;
    expect(average, isA<double>());
    expect(average, 80);
  });
}
output
00:00 +0: loading test/isa_test.dart
00:00 +0: checking a type
00:00 +1: All tests passed!

/ always gives a double, and 80.0 == 80 is true, so both checks pass.


9. reason: — Saying Why

expect takes an optional reason: that is printed when the check fails:

dart
import 'package:test/test.dart';

void main() {
  test('reason', () {
    var balance = 500;
    expect(balance - 700, greaterThan(0), reason: 'Ali withdrew 700 from 500');
  });
}
output
00:00 +0 -1: reason [E]
  Expected: a value greater than <0>
    Actual: <-200>
     Which: is not a value greater than <0>
  Ali withdrew 700 from 500

Use it when the values alone would not explain what went wrong.


10. group() and Test Names That Read as Sentences

group(description, body) collects related tests. Dart joins the group's name and the test's name, so write them to form a sentence:

dart
import 'package:marks_app/marks_app.dart';
import 'package:test/test.dart';

void main() {
  group('grade', () {
    test('gives A for 80 and above', () {
      expect(grade(80), 'A');
    });

    test('gives B for 60 to 79', () {
      expect(grade(79), 'B');
    });

    test('gives C below 60', () {
      expect(grade(59), 'C');
    });
  });
}
output
$ dart test
00:00 +0: loading test/marks_app_test.dart
00:00 +0: test/marks_app_test.dart: grade gives A for 80 and above
00:00 +1: test/marks_app_test.dart: grade gives B for 60 to 79
00:00 +2: test/marks_app_test.dart: grade gives C below 60
00:00 +3: All tests passed!

"grade gives B for 60 to 79" is a specification anyone can read; "test 2" tells nobody anything. We will organize larger test files, with setUp and shared data, in Lecture 122.


11. Reading a Failure Report

Suppose someone changes mark >= 80 to mark > 80 in grade. Run dart test again:

output
00:00 +0: test/marks_app_test.dart: grade gives A for 80 and above
00:00 +0 -1: test/marks_app_test.dart: grade gives A for 80 and above [E]
  Expected: 'A'
    Actual: 'B'
     Which: is different.
            Expected: A
              Actual: B
                      ^
             Differ at offset 0
  
  package:matcher               expect
  test/marks_app_test.dart 7:7  main.<fn>.<fn>
  
00:00 +0 -1: test/marks_app_test.dart: grade gives B for 60 to 79
00:00 +1 -1: test/marks_app_test.dart: grade gives C below 60
00:00 +2 -1: Some tests failed.

Failing tests:
  test/marks_app_test.dart: grade gives A for 80 and above

(The loading line and two hint lines at the end are left out.) Read it top to bottom:

  1. +0 -1 — none passed, one failed so far. [E] marks the failing test.
  2. The name says what is broken: grade gives A for 80 and above.
  3. Expected / Actual shows the values; Which explains the difference.
  4. marks_app_test.dart 7:7 is the file, line and column of the failing expect.
  5. The other tests still run, and the summary lists every failure.

12. Running a Single Test or Group

--name (short -n) runs only tests whose full name contains the text. dart test --name grade runs the whole grade group; a longer text picks one test:

output
$ dart test -n 'gives B'
00:00 +0: loading test/marks_app_test.dart
00:00 +0: test/marks_app_test.dart: grade gives B for 60 to 79
00:00 +1: All tests passed!

--name treats its text as a regular expression, where +, ( and . have special meanings. To match the text exactly as typed, use --plain-name (short -N):

output
$ dart test -n '0.1 + 0.2'
00:00 +0: loading test/sum_test.dart
No tests ran.
No tests match regular expression "0.1 + 0.2".
output
$ dart test -N '0.1 + 0.2'
00:00 +0: loading test/sum_test.dart
00:00 +0: test/sum_test.dart: 0.1 + 0.2 is close to 0.3
00:00 +1: All tests passed!

13. A Real-World Example

Add a report line to lib/marks_app.dart:

dart
String report(String name, List<int> marks) =>
    '$name: total ${total(marks)}, grade ${grade(average(marks).round())}';

Then test every function, one group each:

dart
import 'package:marks_app/marks_app.dart';
import 'package:test/test.dart';

void main() {
  var marks = [80, 75, 85];

  group('total', () {
    test('adds every mark', () {
      expect(total(marks), 240);
    });
  });

  group('average', () {
    test('is a double', () {
      expect(average(marks), isA<double>());
    });

    test('of 70, 71 and 71 is about 70.67', () {
      expect(average([70, 71, 71]), closeTo(70.67, 0.01));
    });
  });

  group('grade', () {
    test('gives A from 80 upwards', () {
      expect(grade(80), 'A', reason: '80 is the lowest A');
    });
  });

  group('report', () {
    test('starts with the name and shows the total and grade', () {
      var line = report('Ali', marks);

      expect(line, startsWith('Ali'));
      expect(line, contains('total 240'));
      expect(line, contains('grade A'));
    });
  });
}
output
$ dart test
00:00 +0: loading test/marks_app_test.dart
00:00 +0: test/marks_app_test.dart: total adds every mark
00:00 +1: test/marks_app_test.dart: average is a double
00:00 +2: test/marks_app_test.dart: average of 70, 71 and 71 is about 70.67
00:00 +3: test/marks_app_test.dart: grade gives A from 80 upwards
00:00 +4: test/marks_app_test.dart: report starts with the name and shows the total and grade
00:00 +5: All tests passed!

Five named facts about the code, checked in under a second. Change anything in lib/ and one command tells you whether they still hold.


Code Examples

Each example is one file in test/. To keep it self-contained, the function under test sits in the test file; in a real project it lives in lib/.

Example 1 — Arrange, Act, Assert

dart
import 'package:test/test.dart';

void main() {
  test('price times quantity gives the total', () {
    var price = 450;
    var quantity = 2;

    var total = price * quantity;

    expect(total, 900);
  });
}
output
00:00 +0: loading test/e1_test.dart
00:00 +0: price times quantity gives the total
00:00 +1: All tests passed!

Example 2 — equals

dart
import 'package:test/test.dart';

void main() {
  test('toUpperCase turns ali into ALI', () {
    var name = 'ali';

    var result = name.toUpperCase();

    expect(result, equals('ALI'));
  });
}
output
00:00 +0: loading test/e2_test.dart
00:00 +0: toUpperCase turns ali into ALI
00:00 +1: All tests passed!

Example 3 — isTrue and isFalse

dart
import 'package:test/test.dart';

void main() {
  test('10 is even and 7 is not', () {
    expect(10.isEven, isTrue);
    expect(7.isEven, isFalse);
  });
}
output
00:00 +0: loading test/e3_test.dart
00:00 +0: 10 is even and 7 is not
00:00 +1: All tests passed!

Example 4 — isNull and isNotNull

dart
import 'package:test/test.dart';

void main() {
  test('tryParse gives null for text that is not a number', () {
    expect(int.tryParse('abc'), isNull);
    expect(int.tryParse('42'), isNotNull);
  });
}
output
00:00 +0: loading test/e4_test.dart
00:00 +0: tryParse gives null for text that is not a number
00:00 +1: All tests passed!

Example 5 — closeTo

dart
import 'package:test/test.dart';

void main() {
  test('three items of 1.1 cost about 3.3', () {
    var total = 1.1 * 3;

    expect(total, closeTo(3.3, 0.001));
  });
}
output
00:00 +0: loading test/e5_test.dart
00:00 +0: three items of 1.1 cost about 3.3
00:00 +1: All tests passed!

1.1 * 3 is 3.3000000000000003, so expect(total, 3.3) would fail.

Example 6 — List and String Matchers

dart
import 'package:test/test.dart';

void main() {
  test('the class list has three students', () {
    var students = ['Ali', 'Sara', 'Zayan'];

    expect(students, hasLength(3));
    expect(students, contains('Zayan'));
    expect(students.first, startsWith('A'));
    expect(students.where((s) => s.length > 5), isEmpty);
  });
}
output
00:00 +0: loading test/e6_test.dart
00:00 +0: the class list has three students
00:00 +1: All tests passed!

Example 7 — Student Marks

dart
import 'package:test/test.dart';

String grade(int mark) {
  if (mark >= 80) return 'A';
  if (mark >= 60) return 'B';
  return 'C';
}

void main() {
  group('grade', () {
    test('gives A for 80', () {
      expect(grade(80), 'A');
    });

    test('gives B for 79', () {
      expect(grade(79), 'B');
    });

    test('gives C for 59', () {
      expect(grade(59), 'C');
    });
  });
}
output
00:00 +0: loading test/e7_test.dart
00:00 +0: grade gives A for 80
00:00 +1: grade gives B for 79
00:00 +2: grade gives C for 59
00:00 +3: All tests passed!

The inputs sit right on the edges — 80, 79, 59 — because that is where a > versus >= mistake hides.

Example 8 — Shopping Total

dart
import 'package:test/test.dart';

double cartTotal(List<double> prices) {
  var sum = 0.0;
  for (var price in prices) {
    sum += price;
  }
  return sum;
}

void main() {
  group('cartTotal', () {
    test('adds the price of every item', () {
      var prices = [99.99, 150.0, 49.98];

      var total = cartTotal(prices);

      expect(total, closeTo(299.97, 0.001));
    });

    test('is 0 for an empty cart', () {
      expect(cartTotal([]), 0, reason: 'an empty cart costs nothing');
    });
  });
}
output
00:00 +0: loading test/e8_test.dart
00:00 +0: cartTotal adds the price of every item
00:00 +1: cartTotal is 0 for an empty cart
00:00 +2: All tests passed!

Example 9 — Bank Balance

dart
import 'package:test/test.dart';

double withdraw(double balance, double amount) => balance - amount;

void main() {
  test('withdrawing 250 from 1000 leaves 750', () {
    var balance = 1000.0;

    var result = withdraw(balance, 250);

    expect(result, isA<double>());
    expect(result, 750);
    expect(result, greaterThanOrEqualTo(0), reason: 'balance went negative');
  });
}
output
00:00 +0: loading test/e9_test.dart
00:00 +0: withdrawing 250 from 1000 leaves 750
00:00 +1: All tests passed!

Example 10 — Temperature Conversion

dart
import 'package:test/test.dart';

double toFahrenheit(double celsius) => celsius * 9 / 5 + 32;

void main() {
  group('toFahrenheit', () {
    test('turns 0 into 32', () {
      expect(toFahrenheit(0), 32);
    });

    test('turns 100 into 212', () {
      expect(toFahrenheit(100), 212);
    });

    test('turns 36.6 into about 97.9', () {
      expect(toFahrenheit(36.6), closeTo(97.88, 0.01));
    });
  });
}
output
$ dart test test/e10_test.dart -n 'turns 36'
00:00 +0: loading test/e10_test.dart
00:00 +0: toFahrenheit turns 36.6 into about 97.9
00:00 +1: All tests passed!

Common Beginner Mistakes

1. Swapping actual and expected

dart
// expect(240, total([80, 75, 80])); // ✗ expected value first
output
  Expected: <235>
    Actual: <240>

The report now says the code produced 240 — backwards. The real result always goes first: expect(total(...), 240).

2. Forgetting the import

A test file without import 'package:test/test.dart'; does not even load:

output
  test/noimp_test.dart:3:5: Error: Method not found: 'expect'.
  test/noimp_test.dart:2:3: Error: Method not found: 'test'.

3. Hiding the values behind isTrue

dart
// expect(students.length == 3, isTrue); // ✗
output
  Expected: true
    Actual: <false>

You learn nothing about the list. expect(students, hasLength(3)) reports Which: has length of <2>.

4. Forgetting that string matchers are case-sensitive

dart
// expect('Total: 240', startsWith('total')); // ✗
output
  Expected: a string starting with 'total'
    Actual: 'Total: 240'

Best Practices

  • Follow Arrange, Act, Assert; put the actual value first in expect.
  • Choose the most specific matcher.
  • Test edge values — 0, empty lists, and exact boundaries like 80 and 79.
  • Name groups after the function and tests after the behaviour.
  • Run one group with -n while you work, then the whole suite before you finish.

Mini Challenge

Write a group for bool isPassing(int mark) => mark >= 50; with three tests: 50 passes, 100 passes, 49 fails. Use isTrue and isFalse.

Try it yourself before opening the solution.

Show solution
dart
import 'package:test/test.dart';

bool isPassing(int mark) => mark >= 50;

void main() {
  group('isPassing', () {
    test('is true for a mark of 50', () {
      expect(isPassing(50), isTrue);
    });

    test('is true for a mark of 100', () {
      expect(isPassing(100), isTrue);
    });

    test('is false for a mark of 49', () {
      expect(isPassing(49), isFalse);
    });
  });
}
output
00:00 +0: loading test/mini_test.dart
00:00 +0: isPassing is true for a mark of 50
00:00 +1: isPassing is true for a mark of 100
00:00 +2: isPassing is false for a mark of 49
00:00 +3: All tests passed!

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Write a test named '7 times 6 is 42' that checks 7 * 6.
  2. Write a test that checks 'Dart' has a length of 4, using hasLength.
  3. Which matcher fits each check: (a) a calculated double is about 2.5, (b) a map lookup found nothing, (c) a message begins with 'Error', (d) a value is a String?

Application

  1. Write a test that checks int.tryParse('12a') gives null.
  2. expect(0.7 + 0.1, 0.8) fails. Fix the test so it passes.
  3. For var team = ['Ali', 'Sara', 'Zayan'];, write one test checking it has 3 members, contains 'Zayan', and that its last name ends with 'n'.
  4. A test named 'works' sits inside group('cartTotal', …). Give it a better name and write the full name dart test would print.
  5. Write the command that runs only the tests in group('average', …), and the command that runs only a test named 'total + tax is correct'.

Challenge

  1. Read this report and answer: which test failed, what did the code produce, and on which line is the failing expect?

    output
    00:00 +0 -1: balance grows by the deposit [E]
      Expected: <800>
        Actual: <700>
    
      package:matcher         expect
      test/q9_test.dart 10:7  main.<fn>.<fn>
  2. Write a group for double applyDiscount(double price, double percent) => price - price * percent / 100; with three tests: 20% off 1500 is 1200; 0% leaves 1500 (with a reason:); 15% off 99.99 is about 84.99.


Practice Answers

Question 1 — Solution

dart
import 'package:test/test.dart';

void main() {
  test('7 times 6 is 42', () {
    expect(7 * 6, 42);
  });
}
output
00:00 +0: loading test/q1_test.dart
00:00 +0: 7 times 6 is 42
00:00 +1: All tests passed!

Question 2 — Solution

dart
import 'package:test/test.dart';

void main() {
  test('Dart has four letters', () {
    expect('Dart', hasLength(4));
  });
}
output
00:00 +0: loading test/q2_test.dart
00:00 +0: Dart has four letters
00:00 +1: All tests passed!

hasLength works on strings too.

Question 3 — Solution

(a) closeTo(2.5, 0.001), (b) isNull, (c) startsWith('Error'), (d) isA<String>().

Question 4 — Solution

dart
import 'package:test/test.dart';

void main() {
  test('tryParse gives null for 12a', () {
    expect(int.tryParse('12a'), isNull);
  });
}
output
00:00 +0: loading test/q4_test.dart
00:00 +0: tryParse gives null for 12a
00:00 +1: All tests passed!

Question 5 — Solution

The real failure is Actual: <0.7999999999999999>. Use closeTo:

dart
import 'package:test/test.dart';

void main() {
  test('0.7 + 0.1 is close to 0.8', () {
    expect(0.7 + 0.1, closeTo(0.8, 0.0001));
  });
}
output
00:00 +0: loading test/q5_test.dart
00:00 +0: 0.7 + 0.1 is close to 0.8
00:00 +1: All tests passed!

Question 6 — Solution

dart
import 'package:test/test.dart';

void main() {
  test('the team has three members and includes Zayan', () {
    var team = ['Ali', 'Sara', 'Zayan'];

    expect(team, hasLength(3));
    expect(team, contains('Zayan'));
    expect(team.last, endsWith('n'));
  });
}
output
00:00 +0: loading test/q6_test.dart
00:00 +0: the team has three members and includes Zayan
00:00 +1: All tests passed!

Question 7 — Solution

For example test('adds the price of every item', …). The full name is cartTotal adds the price of every item.

Question 8 — Solution

output
dart test -n average
dart test -N 'total + tax is correct'

-N is needed because + is special in a regular expression.

Question 9 — Solution

The test balance grows by the deposit failed. The code produced 700, not 800. The failing expect is on line 10, column 7 of test/q9_test.dart.

Question 10 — Solution

dart
import 'package:test/test.dart';

double applyDiscount(double price, double percent) =>
    price - price * percent / 100;

void main() {
  group('applyDiscount', () {
    test('takes 20 percent off 1500', () {
      expect(applyDiscount(1500, 20), 1200);
    });

    test('leaves the price alone for 0 percent', () {
      expect(applyDiscount(1500, 0), 1500, reason: '0% must change nothing');
    });

    test('takes 15 percent off 99.99', () {
      expect(applyDiscount(99.99, 15), closeTo(84.99, 0.01));
    });
  });
}
output
00:00 +0: loading test/q10_test.dart
00:00 +0: applyDiscount takes 20 percent off 1500
00:00 +1: applyDiscount leaves the price alone for 0 percent
00:00 +2: applyDiscount takes 15 percent off 99.99
00:00 +3: All tests passed!

The exact result, 84.9915, is within 0.01 of 84.99.


Quick Revision

  1. What is a unit?
  2. What happens in Arrange, Act and Assert?
  3. Which matcher does expect(x, 5) use?
  4. Why does expect(0.1 + 0.2, 0.3) fail, and what fixes it?
  5. Which matchers check part of a string?
  6. How does dart test build a test's full name inside a group?
  7. What is the difference between -n and -N?

Key Takeaways

  • A unit test checks one small piece of code, structured as Arrange, Act, Assert.
  • The second argument of expect is a matcher; a plain value means equals.
  • Test calculated doubles with closeTo, never exact equality.
  • Specific matchers (hasLength, contains, startsWith, isA<T>()) explain failures better than isTrue; reason: adds your own explanation.
  • group() names join with test names — write them to read as sentences.
  • dart test -n filters by regular expression, -N by plain text.
  • A failure report gives the test name, Expected / Actual / Which, and the file and line.

What's Next?

Lecture 120 — Testing Functions & Classes. So far we tested small functions, one value at a time. Real programs are built from classes whose objects change as you use them — a bank account after three deposits, a cart after removing an item. Next we will test functions with many cases and test objects and their state.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.