Testing jobs
Treat processors like ordinary async functions first, then add one small integration check for registration and webhook reachability.
Unit-test processor code#
Put the business logic in a plain function, then call it from the worker. Most tests should hit that function directly without NextMQ, HTTP, or Redis.
import { Queue, Worker, type Job } from '@nextmq/sdk'
type ReportJob = { reportId: string }
export const reportQueue = new Queue<ReportJob>('reports')
export async function buildReport(data: ReportJob) {
if (!data.reportId) throw new Error('reportId is required')
return { url: await renderReport(data.reportId) }
}
export async function processReport(job: Job<ReportJob>) {
await job.updateProgress(25)
const result = await buildReport(job.data)
await job.updateProgress(100)
return result
}
export const reportWorker = new Worker('reports', processReport)import { expect, it, vi } from 'vitest'
import { buildReport, processReport } from './reports'
it('validates inputs without the queue', async () => {
await expect(buildReport({ reportId: '' })).rejects.toThrow('reportId')
})
it('reports progress from the processor', async () => {
const updateProgress = vi.fn()
await processReport({
id: 'job-1',
data: { reportId: 'r_123' },
updateProgress,
} as any)
expect(updateProgress).toHaveBeenCalledWith(25)
expect(updateProgress).toHaveBeenCalledWith(100)
})Test idempotency#
Reliability bugs usually come from side effects that run twice. Write a test that calls the processor twice with the same job.id.
it('does not charge twice for the same job id', async () => {
const job = { id: 'job-42', data: { orderId: 'ord_42' } } as any
await payoutProcessor(job)
await payoutProcessor(job)
expect(bank.transfer).toHaveBeenCalledTimes(1)
})Check registration in CI or smoke tests#
A deployed app should answer the handler health path with registered workers. That verifies environment variables, worker construction, and the callback URL in one request.
curl -fsS https://your-app.com/api/nextmq/healthLocal end-to-end#
NextMQ calls your app over the public internet. For local E2E, expose the local Next.js server through a tunnel and set NEXTMQ_WEBHOOK_BASE_URL to that origin — see Local development for ngrok and Cloudflare Tunnel setup.
NEXTMQ_CONNECTION_STRING=nextmq://v1....
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXTMQ_WEBHOOK_BASE_URL=https://your-tunnel.ngrok.appWhat to mock#
| Thing | Recommended test |
|---|---|
| Processor business logic | Call your function directly with normal objects. |
Job methods | Mock only the methods you call: updateProgress, log, getChildrenValues. |
| Queue producer code | Assert add/addBulk was called with the expected name, data, and opts. |
| Webhook route | Smoke test /api/nextmq/health in a deployed environment. |
| Full queue lifecycle | Use a tunnel and a real project for one E2E path. |
Useful assertions#
- Retries do not duplicate irreversible side effects.
- Permanent validation errors throw
UnrecoverableJobError. - Provider 429s throw
RateLimitErrorwith the expected delay. - Startup registration does not throw in Node runtime.
- The handler route is not deployed to the Edge runtime.
For production failure modes, see Reliability & delivery guarantees.