Test with InMemoryBroker
You’ll test tasks end to end without Redis: enqueue, let a real worker run them in the test isolate, and assert on results, retries and failures.
Prerequisites
Section titled “Prerequisites”testindev_dependencies.- Task registration in a function you can call on a test app.
1. Build a test app
Section titled “1. Build a test app”import 'package:gisila_queue/gisila_queue.dart';import 'package:test/test.dart';
GisilaQueue testApp() => GisilaQueue( broker: InMemoryBroker(pollInterval: const Duration(milliseconds: 5)), backend: InMemoryResultBackend(), logger: SilentQueueLogger(), );A short pollInterval keeps tests fast.
2. Run a worker per test
Section titled “2. Run a worker per test”void main() { late GisilaQueue app; late Worker worker;
setUp(() { app = testApp(); registerTasks(app); worker = app.worker(concurrency: 2)..run(); });
tearDown(() async { await worker.stop(); await app.close(); });
test('adds numbers', () async { final r = await app.send<int>('math.add', args: [2, 3]); expect(await r.get(timeout: const Duration(seconds: 2)), 5); expect(await r.state, TaskState.success); });}run() returns a future that completes on stop(); don’t await it in setUp.
3. Test retries
Section titled “3. Test retries”Use tiny backoffs and no jitter so the test is quick and deterministic:
test('retries then succeeds', () async { var calls = 0; final flaky = app.task<String>('test.flaky', (ctx) async { calls++; if (calls < 3) ctx.retry(countdown: const Duration(milliseconds: 10)); return 'ok after ${ctx.retries} retries'; }, maxRetries: 5, retryJitter: false);
final r = await flaky.delay(); expect(await r.get(timeout: const Duration(seconds: 2)), 'ok after 2 retries'); expect(calls, 3);});4. Test failures
Section titled “4. Test failures”test('records failure', () async { app.task<void>('test.boom', (ctx) async => throw StateError('nope')); final r = await app.send<void>('test.boom'); await expectLater( r.get(timeout: const Duration(seconds: 2)), throwsA(isA<TaskFailureException>()), ); // The failure is stored just before the message is dead-lettered. await Future<void>.delayed(const Duration(milliseconds: 50)); expect(await app.broker.length('dead_letter'), 1);});5. Drive Beat deterministically
Section titled “5. Drive Beat deterministically”Call tick(at) instead of run():
test('heartbeat fires every minute', () async { // A queue the test worker doesn't consume, so the messages stay countable. final beat = Beat(app) ..schedule('hb', 'health.ping', Schedule.every(const Duration(minutes: 1)), queue: 'beat-test'); final t0 = DateTime(2026, 1, 1, 12); await beat.tick(t0); await beat.tick(t0.add(const Duration(seconds: 30))); await beat.tick(t0.add(const Duration(minutes: 1))); expect(await app.broker.length('beat-test'), 2);});6. Mind the differences from Redis
Section titled “6. Mind the differences from Redis”InMemoryResultBackendreturns the handler’s object as is; Redis returns it through JSON. A task returningList<int>passesAsyncResult<List<int>>.get()in memory and fails on Redis. Prefer JSON shapes.InMemoryBrokerhas no visibility timeout, so redelivery isn’t exercised. To test it, subclassInMemoryBrokerand overridevisibilityTimeout,touchandack.- Arguments are still validated as JSON at
send, so unencodable args fail in tests too.
Verify it works
Section titled “Verify it works”dart testNext steps
Section titled “Next steps”- Events: share an
InMemoryEventBusto assert on emitted events. - Workers reference