search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs
You are viewing an older version (1.0.0-alpha.10). Go to the latest.

Facade Testing

Introduction

Magic provides a first-class facade faking API for Auth, Cache, Vault, and Log. Each facade exposes a fake() static method that swaps the IoC-bound service with an in-memory implementation for the duration of a test. No real credentials, storage, or output is touched.

Call fake() in setUp and unfake() in tearDown:

setUp(() {
  MagicApp.reset();
  Magic.flush();

  final authFake = Auth.fake();
  final cacheFake = Cache.fake();
  final vaultFake = Vault.fake();
  final logFake = Log.fake();
});

tearDown(() {
  Auth.unfake();
  Cache.unfake();
  Vault.unfake();
  Log.unfake();
});

Each fake() call returns its fake instance so you can run assertions after the code under test executes.

Auth.fake()

Auth.fake() replaces the real AuthManager with a FakeAuthManager that routes all guard operations through an in-memory _FakeGuard. No platform channels, no secure storage, no token refresh calls.

Signature:

static FakeAuthManager fake({Authenticatable? user})

Pass user to pre-authenticate before the test body runs.

Basic Usage

test('dashboard redirects guests to login', () {
  Auth.fake(); // No user — guest state

  expect(Auth.check(), isFalse);
  expect(Auth.guest, isTrue);
});

test('user is pre-authenticated', () {
  final user = User()..fill({'id': 1, 'name': 'Alice'});
  Auth.fake(user: user);

  expect(Auth.check(), isTrue);
  expect(Auth.user()?.name, 'Alice');
});

Assertions

Method Description
fake.assertLoggedIn() Assert a user is currently authenticated.
fake.assertLoggedOut() Assert no user is currently authenticated.
fake.assertLoginAttempted() Assert at least one login call was made.
fake.assertLoginCount(int expected) Assert an exact number of login calls.
test('controller logs in user on success', () async {
  final user = User()..fill({'id': 1, 'name': 'Alice'});
  final fake = Auth.fake();

  await Auth.login({'token': 'test-token'}, user);

  fake.assertLoggedIn();
  fake.assertLoginAttempted();
  fake.assertLoginCount(1);
});

test('controller logs out user', () async {
  final user = User()..fill({'id': 1, 'name': 'Alice'});
  final fake = Auth.fake(user: user);

  await Auth.logout();

  fake.assertLoggedOut();
});

Resetting State

Call fake.reset() to clear the current user, token, and login attempt records without restoring the real driver:

fake.reset(); // Clears user, token, and login attempt history

Cache.fake()

Cache.fake() replaces the real CacheManager with a FakeCacheManager backed by a plain Map. All operations are synchronous and in-memory. Every put, get, and forget call is recorded in fake.recorded.

Signature:

static FakeCacheManager fake()

Basic Usage

test('controller caches user list', () async {
  final fake = Cache.fake();

  await Cache.put('users', ['Alice', 'Bob'], ttl: Duration(minutes: 5));

  expect(Cache.has('users'), isTrue);
  expect(Cache.get('users'), equals(['Alice', 'Bob']));
});

Assertions

Method Description
fake.assertHas(String key) Assert that the key currently exists in the cache.
fake.assertMissing(String key) Assert that the key does not exist in the cache.
fake.assertPut(String key) Assert that the key was stored via put at least once.
test('user list is cached after fetch', () async {
  final fake = Cache.fake();

  await Cache.put('users', ['Alice', 'Bob']);

  fake.assertHas('users');
  fake.assertPut('users');
});

test('cache is cleared after flush', () async {
  final fake = Cache.fake();

  await Cache.put('users', ['Alice']);
  await Cache.flush();

  fake.assertMissing('users');
});

Recorded Operations

Access fake.recorded for a full chronological list of cache operations:

final fake = Cache.fake();

await Cache.put('a', 1);
await Cache.get('a');
await Cache.forget('a');

expect(fake.recorded[0].operation, 'put');
expect(fake.recorded[1].operation, 'get');
expect(fake.recorded[2].operation, 'forget');

Each entry is a CacheRecord record type: ({String operation, String key, dynamic value}).

Resetting State

Call fake.reset() to clear both the in-memory store and the recorded operations list:

fake.reset();

Vault.fake()

Vault.fake() replaces the real MagicVaultService (backed by flutter_secure_storage) with a FakeVaultService that stores data in a plain Map. No platform channels are invoked.

Signature:

static FakeVaultService fake([Map initialValues = const {}])

Pass initialValues to pre-seed the vault before the test body runs.

Basic Usage

test('controller reads token from vault', () async {
  Vault.fake({'auth_token': 'seed-token'});

  final token = await Vault.get('auth_token');

  expect(token, 'seed-token');
});

test('controller writes token to vault', () async {
  final fake = Vault.fake();

  await Vault.put('auth_token', 'abc123');

  expect(await Vault.get('auth_token'), 'abc123');
});

Assertions

Method Description
fake.assertWritten(String key) Assert that key was written via put at least once.
fake.assertDeleted(String key) Assert that key was deleted via delete at least once.
fake.assertContains(String key) Assert that key currently exists in the store.
fake.assertMissing(String key) Assert that key does not currently exist in the store.
test('logout clears auth token', () async {
  final fake = Vault.fake({'auth_token': 'abc123'});

  await Vault.delete('auth_token');

  fake.assertDeleted('auth_token');
  fake.assertMissing('auth_token');
});

test('login stores token in vault', () async {
  final fake = Vault.fake();

  await Vault.put('auth_token', 'new-token');

  fake.assertWritten('auth_token');
  fake.assertContains('auth_token');
});

Recorded Operations

Access fake.recorded for a full list of vault operations:

final fake = Vault.fake();

await Vault.put('token', 'abc');
await Vault.get('token');
await Vault.delete('token');

expect(fake.recorded[0].operation, 'put');
expect(fake.recorded[1].operation, 'get');
expect(fake.recorded[2].operation, 'remove');

Each entry is a VaultOperation record type: ({String operation, String key}).

Resetting State

Call fake.reset() to clear the in-memory store and recorded operations:

fake.reset();

Log.fake()

Log.fake() replaces the real LogManager with a FakeLogManager that captures all log entries in memory instead of writing to the console.

Signature:

static FakeLogManager fake()

Basic Usage

test('controller logs error on failure', () async {
  final fake = Log.fake();

  Log.error('Payment failed', {'order': 42});

  fake.assertLoggedError('Payment failed');
});

Assertions

Method Description
fake.assertLogged(String level, String message) Assert at least one entry matches both level and message.
fake.assertLoggedError(String message) Shorthand for assertLogged('error', message).
fake.assertNothingLogged([String? level]) Assert no entries recorded. If level is given, assert no entries at that level.
fake.assertLoggedCount(int expected) Assert an exact total number of entries recorded.
test('no logs emitted during normal operation', () {
  final fake = Log.fake();

  doNormalWork();

  fake.assertNothingLogged();
});

test('exactly one error is logged', () async {
  final fake = Log.fake();

  Log.error('Something failed');

  fake.assertLoggedCount(1);
  fake.assertLoggedError('Something failed');
});

test('warning is logged at correct level', () {
  final fake = Log.fake();

  Log.warning('Rate limit approaching');

  fake.assertLogged('warning', 'Rate limit approaching');
  fake.assertNothingLogged('error'); // No errors logged
});

Inspecting Entries

Access fake.entries for the full list of captured log entries:

final fake = Log.fake();

Log.info('User logged in', {'id': 1});
Log.error('Token expired');

expect(fake.entries.length, 2);
expect(fake.entries[0].level, 'info');
expect(fake.entries[0].message, 'User logged in');
expect(fake.entries[1].level, 'error');

Each entry is a FakeLogEntry record type: ({String level, String message, dynamic context}).

Resetting State

Call fake.reset() to clear all captured entries without restoring the real driver:

fake.reset();

Full Example

A controller that integrates Auth, Cache, Vault, and Log, with tests covering all four fakes:

// lib/controllers/session_controller.dart
class SessionController extends MagicController {
  Future login(String token, User user) async {
    await Auth.login({'token': token}, user);
    await Vault.put('auth_token', token);
    await Cache.put('current_user', user.toMap());
    Log.info('User logged in', {'id': user.id});
  }

  Future logout() async {
    await Auth.logout();
    await Vault.delete('auth_token');
    await Cache.forget('current_user');
    Log.info('User logged out');
  }
}

// test/http/session_controller_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:magic/magic.dart';

void main() {
  group('SessionController', () {
    late SessionController controller;
    late FakeAuthManager authFake;
    late FakeCacheManager cacheFake;
    late FakeVaultService vaultFake;
    late FakeLogManager logFake;

    setUp(() {
      MagicApp.reset();
      Magic.flush();

      controller = SessionController();
      authFake = Auth.fake();
      cacheFake = Cache.fake();
      vaultFake = Vault.fake();
      logFake = Log.fake();
    });

    tearDown(() {
      Auth.unfake();
      Cache.unfake();
      Vault.unfake();
      Log.unfake();
    });

    test('login authenticates user and stores credentials', () async {
      final user = User()..fill({'id': 1, 'name': 'Alice'});

      await controller.login('token-abc', user);

      authFake.assertLoggedIn();
      authFake.assertLoginCount(1);
      vaultFake.assertWritten('auth_token');
      vaultFake.assertContains('auth_token');
      cacheFake.assertHas('current_user');
      cacheFake.assertPut('current_user');
      logFake.assertLogged('info', 'User logged in');
    });

    test('logout clears all session state', () async {
      final user = User()..fill({'id': 1, 'name': 'Alice'});
      authFake = Auth.fake(user: user);
      vaultFake = Vault.fake({'auth_token': 'token-abc'});

      await controller.logout();

      authFake.assertLoggedOut();
      vaultFake.assertDeleted('auth_token');
      vaultFake.assertMissing('auth_token');
      cacheFake.assertMissing('current_user');
      logFake.assertLogged('info', 'User logged out');
    });
  });
}

Unfaking

Call unfake() in tearDown to remove the fake from the IoC container and restore the real service binding for subsequent tests:

tearDown(() {
  Auth.unfake();
  Cache.unfake();
  Vault.unfake();
  Log.unfake();
});

After unfake(), the next facade call resolves the original singleton binding as if fake() was never called. This is the recommended pattern when each test should start from a clean real-service state.

If you want to reuse the same fake across multiple tests in a group without restoring the real driver, call fake.reset() instead:

final logFake = Log.fake(); // Install once for the group

test('first', () {
  Log.error('a');
  logFake.assertLoggedCount(1);
  logFake.reset(); // Clear for next test — fake remains installed
});

test('second', () {
  Log.error('b');
  logFake.assertLoggedCount(1);
});

See Also

  • HTTP TestsHttp.fake(), URL pattern stubs, request assertions