Skip to content

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.

  • test in dev_dependencies.
  • Task registration in a function you can call on 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.

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.

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);
});
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);
});

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);
});
  • InMemoryResultBackend returns the handler’s object as is; Redis returns it through JSON. A task returning List<int> passes AsyncResult<List<int>>.get() in memory and fails on Redis. Prefer JSON shapes.
  • InMemoryBroker has no visibility timeout, so redelivery isn’t exercised. To test it, subclass InMemoryBroker and override visibilityTimeout, touch and ack.
  • Arguments are still validated as JSON at send, so unencodable args fail in tests too.
Terminal window
dart test