๐Ÿงช Lesson 10: Testing Your Code

โฑ 30-40 min ๐Ÿ“Š Intermediate ๐Ÿ“– In-depth tutorial ๐Ÿ”— Official Source
1

Why Testing Matters โ€” The Safety Net

๐Ÿง  Tests verify your code works correctly โ€” and keeps working when you make changes

Without tests, every code change is a gamble. With tests, you have a safety net that catches regressions instantly. Dart provides the test package for writing and running tests.

โŒ Without Tests

  • Manually verify every change
  • Fear refactoring โ€” "what if I break something?"
  • Bugs discovered by users
  • No documentation of expected behavior
  • Slow, unreliable verification

โœ… With Tests

  • Run all tests in seconds with dart test
  • Refactor confidently โ€” tests tell you if something broke
  • Bugs caught before they reach users
  • Tests serve as living documentation
  • Fast, consistent, automated verification
๐Ÿ“Œ The Testing Mindset: Write tests BEFORE fixing a bug (to confirm the bug exists), then AFTER fixing it (to prevent it from returning). This is called regression testing.
2

The Anatomy of a Dart Test

๐Ÿง  Every test has three parts: import the library, define the test, assert expectations

The test package provides test() to define individual tests and expect() to make assertions about what should happen.

Basic Test Structure โ€” Line by Line

// 1. Import the test package
import 'package:test/test.dart';

// 2. The code you want to test
int add(int a, int b) => a + b;

// 3. The main() function โ€” where tests run from
void main() {
  // 4. Define a test with a descriptive name
  test('adds two positive numbers', () {
    // 5. The actual value and expected value
    final result = add(2, 3);
    // 6. Assert that result matches expectation
    expect(result, equals(5));
  });
}

๐Ÿ“ Breakdown

1 Import from package:test/test.dart
4 The function being tested (your code)
7 main() is the test entry point
9 test() defines one test case
9 String describes what the test checks
11 Call the function with test inputs
13 expect(actual, matcher) asserts

Running Tests

$ dart test 00:00 +0: Loading tests... 00:00 +1: adds two positive numbers 00:00 +1: All tests passed!

Run dart test in your terminal. Each passing test shows a green +.

3

The Official Tutorial Example โ€” Calculator Tests

๐Ÿ“– This complete example comes directly from the official Dart testing tutorial

The official tutorial demonstrates testing with a simple calculator. Here's the full example with explanations of what each part does.

๐Ÿง  The official tutorial shows a complete test file for a calculator with multiple test cases

Below is the example code from the tutorial, broken down into the calculator functions and their tests.

The Calculator Code (What We're Testing)

// calculator.dart โ€” The code under test
class Calculator {
  int add(int a, int b) => a + b;
  int subtract(int a, int b) => a - b;
  int multiply(int a, int b) => a * b;
  double divide(int a, int b) {
    if (b == 0) throw ArgumentError('Cannot divide by zero');
    return a / b;
  }
}

The Complete Test File (from the official tutorial)

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

void main() {
  final calculator = Calculator();
  
  // Test 1: Addition
  test('calculator adds two numbers', () {
    expect(calculator.add(2, 3), equals(5));
    expect(calculator.add(-5, 10), equals(5));
  });
  
  // Test 2: Subtraction
  test('calculator subtracts two numbers', () {
    expect(calculator.subtract(10, 4), equals(6));
  });
  
  // Test 3: Multiplication
  test('calculator multiplies two numbers', () {
    expect(calculator.multiply(3, 4), equals(12));
  });
  
  // Test 4: Division (including the error case!)
  test('calculator divides two numbers', () {
    expect(calculator.divide(10, 2), equals(5.0));
  });
  
  // Test 5: Division by zero throws an error
  test('calculator throws when dividing by zero', () {
    expect(
      () => calculator.divide(10, 0),
      throwsA(isA<ArgumentError>()),
    );
  });
}

๐Ÿ“ What Each Part Does

1 Import the test framework
2 Import the code to test
5 Create calculator instance to test
8-11 Addition test โ€” tests positive AND negative
14-16 Subtraction test โ€” one assertion
19-21 Multiplication test
24-26 Division test โ€” normal case
29-34 Division by zero โ€” expects an error!
31 () => ... wraps function in a closure
32 throwsA(isA<ArgumentError>()) โ€” check error type
๐Ÿ”‘ Key Pattern: Testing Exceptions: Notice the division-by-zero test wraps () => calculator.divide(10, 0) in a closure. This is required because expect() needs to catch the exception when the function runs, not when it's passed as an argument.

Expected Test Output

$ dart test 00:00 +1: calculator adds two numbers 00:00 +2: calculator subtracts two numbers 00:00 +3: calculator multiplies two numbers 00:00 +4: calculator divides two numbers 00:00 +5: calculator throws when dividing by zero 00:00 +5: All tests passed!
4

Matchers โ€” The expect() Power Tools

๐Ÿง  Matchers let you describe what you expect. Dart compares the actual value against the matcher.

The expect() function takes two arguments: the actual value and a matcher. Matchers can check equality, type, ranges, collections, strings, and more.

โš–๏ธ Equality

expect(42, equals(42));
expect('hi', isNot(equals('bye')));
expect(obj, same(expectedObj));

equals() โ€” value equality
isNot() โ€” negate any matcher
same() โ€” identical reference

๐Ÿ”ข Numeric

expect(10, greaterThan(5));
expect(3, lessThan(10));
expect(0.5, closeTo(0.5, 0.01));
expect(5, inInclusiveRange(1, 10));

closeTo() โ€” approximate equality for doubles

๐Ÿ“ Strings

expect('hello', contains('ell'));
expect('dart', startsWith('da'));
expect('end', endsWith('nd'));
expect('Dart', matches(RegExp(r'D.rt')));

๐Ÿ“‹ Collections

expect([1,2,3], contains(2));
expect([], isEmpty);
expect([1,2], isNotEmpty);
expect([1,2], hasLength(2));
expect([1,2], containsAll([2,1]));

๐Ÿท๏ธ Types & Null

expect('hi', isA<String>());
expect(null, isNull);
expect(42, isNotNull);
expect(true, isTrue);
expect(false, isFalse);

๐Ÿ’ฅ Exceptions

expect(
  () => throwFunc(),
  throwsException
);
expect(
  () => badFunc(),
  throwsA(isA<FormatException>())
);
expect(
  () => safeFunc(),
  returnsNormally
);

throwsA() โ€” checks specific exception type

๐Ÿงช Test Matchers Live

Results will appear here...
5

Organizing Tests with Groups

๐Ÿง  Use group() to organize related tests into logical categories

Groups make test output easier to read and let you run subsets of tests. You can nest groups inside groups for hierarchical organization. Each group can have its own setUp() and tearDown() functions.

Group Structure Example

import 'package:test/test.dart';

void main() {
  // Top-level group: Calculator
  group('Calculator', () {
    late Calculator calc;
    
    // setUp runs before EACH test in this group
    setUp(() {
      calc = Calculator();
    });
    
    // Sub-group: addition tests
    group('addition', () {
      test('positive numbers', () {
        expect(calc.add(2, 3), equals(5));
      });
      test('negative numbers', () {
        expect(calc.add(-2, -3), equals(-5));
      });
    });
    
    // Sub-group: division tests
    group('division', () {
      test('normal division', () {
        expect(calc.divide(10, 2), equals(5.0));
      });
      test('division by zero', () {
        expect(
          () => calc.divide(10, 0),
          throwsA(isA<ArgumentError>()),
        );
      });
    });
  });
}
๐Ÿ“Œ setUp() and tearDown(): setUp() runs before every test in its group โ€” perfect for creating fresh objects. tearDown() runs after every test โ€” useful for cleanup like closing files or database connections.

Expected Grouped Output

$ dart test 00:00 +1: Calculator addition positive numbers 00:00 +2: Calculator addition negative numbers 00:00 +3: Calculator division normal division 00:00 +4: Calculator division division by zero 00:00 +4: All tests passed!
6

Testing Asynchronous Code

๐Ÿง  Async tests use the same test() function โ€” just make the callback async

There are two main patterns: use async/await inside the test callback, or use the completion() matcher for Futures.

Pattern 1: async/await (Recommended)

// Make the test callback async, then use await
test('fetchUser returns a User object', () async {
  final user = await fetchUser(1);
  expect(user.id, equals(1));
  expect(user.name, isNotNull);
});

// Testing async errors
test('fetchUser throws on invalid ID', () async {
  expect(
    () async => await fetchUser(-1),
    throwsA(isA<ArgumentError>()),
  );
});

Pattern 2: completion() Matcher

// completion() matches the value a Future will produce
test('fetchUser completes with User', () {
  expect(
    fetchUser(1),
    completion(isA<User>()),
  );
});

๐Ÿงช Simulate Async Test

Select a scenario to see the test result...
7

Testing Exceptions โ€” Ensuring Errors Are Handled

๐Ÿง  When your code should throw an error, your tests should verify it does

Use throwsA() to verify a specific exception type is thrown, or throwsException for any exception. Always wrap the throwing code in a closure () => ....

Exception Testing Patterns

// 1. Test that ANY exception is thrown
test('throws an exception', () {
  expect(
    () => riskyOperation(),
    throwsException,
  );
});

// 2. Test for a SPECIFIC exception type
test('throws FormatException for bad JSON', () {
  expect(
    () => jsonDecode('not valid json'),
    throwsA(isA<FormatException>()),
  );
});

// 3. Test that exception has specific message
test('throws with specific message', () {
  expect(
    () => divide(10, 0),
    throwsA(
      isA<ArgumentError>().having(
        (e) => e.message,
        'message',
        contains('zero'),
      ),
    ),
  );
});

// 4. Test that code does NOT throw
test('returns normally', () {
  expect(
    () => safeOperation(),
    returnsNormally,
  );
});
โš ๏ธ CRITICAL: Always wrap in a closure! If you write expect(divide(10,0), throwsException) without () =>, the exception is thrown BEFORE expect() can catch it. The closure () => divide(10,0) delays execution so the test framework can catch the error.

๐ŸŽฏ Testing Quiz

Score: 0/5 | Hints: 3

Loading question...

๐Ÿš€

Practice Projects

Apply what you've learned by building these hands-on projects.

๐Ÿ“ฆ Project 1 ๐Ÿ“ฆ Project 2