Skip to content

Testing

The testing specification defines how OJS SDKs support testing of application code that enqueues and processes jobs. Three testing modes provide different trade-offs between speed and fidelity.

ModeBackend RequiredExecutionSpeedFidelity
FakeNoIn-memory, no executionFastestLowest
InlineNoSynchronous, same processFastMedium
RealYesFull async, real backendSlowestHighest

Jobs are stored in an in-memory buffer. Handlers are never executed. Use fake mode for unit tests that verify jobs are enqueued correctly.

import { OJS, testing } from '@openjobspec/sdk';
test('creates order and enqueues fulfillment', async () => {
testing.useFakeMode();
await createOrder({ id: 'order_123' });
testing.assertEnqueued('order.fulfill', { args: ['order_123'] });
testing.refuteEnqueued('order.cancel');
});

Jobs are executed synchronously in the same process when enqueued. Use inline mode for integration tests that need to verify handler behavior.

test('fulfillment handler sends email', async () => {
testing.useInlineMode();
await ojs.enqueue('order.fulfill', ['order_123']);
// Handler executed synchronously
expect(emailService.sent).toHaveLength(1);
});

Jobs are sent to a real OJS backend and processed by real workers. Use real mode for end-to-end tests.

func TestPaymentFlow(t *testing.T) {
client := ojs.NewTestClient(t, "http://localhost:8080")
defer client.Cleanup()
client.Enqueue("payment.process", []any{"txn_123"})
client.WaitForCompletion("payment.process", 10*time.Second)
}
AssertionDescription
assert_enqueued(type, opts)Verify a job was enqueued
refute_enqueued(type, opts)Verify a job was NOT enqueued
assert_performed(type, opts)Verify a job was executed (inline/real mode)
assert_completed(type)Verify a job completed successfully
assert_failed(type)Verify a job failed
assert_enqueued_count(type, n)Verify exactly N jobs of type were enqueued

Assertion options can filter by args, queue, priority, and metadata.

Process all pending jobs immediately (inline mode):

OJS::Testing.drain_all
OJS::Testing.drain("email.send") # Drain specific type only

Manipulate time for testing scheduled jobs and retries:

with ojs.testing.freeze_time("2026-02-15T10:00:00Z"):
ojs.enqueue("report.generate", [], scheduled_at="2026-02-15T12:00:00Z")
ojs.testing.advance_time(hours=2)
ojs.testing.drain_all()
assert_performed("report.generate")

Force failures for testing error handling and retry behavior:

testing.FailNext("payment.process", errors.New("connection timeout"))
testing.FailAll("email.send", errors.New("smtp unavailable"))

Each test MUST start with a clean state. SDKs provide:

  • Automatic queue clearing between tests
  • Isolated in-memory stores per test case
  • Cleanup hooks for real mode (delete test jobs)
FeatureFakeInlineReal
Enqueue✅ Buffered✅ Buffered + executed✅ Sent to backend
Handlers❌ Not called✅ Synchronous✅ Async
Retry❌ Not simulated⚠️ Immediate retry✅ Full backoff
Workflows❌ Not simulated⚠️ Sequential execution✅ Full orchestration
Middleware✅ Enqueue chain✅ Both chains✅ Both chains
Scheduling❌ Not simulated⚠️ With time control✅ Real scheduling