NextMQ

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.

jobs/reports.ts
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)
jobs/reports.test.ts
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/health
Heads up
A 200 health response means the workers registered. It does not prove every downstream service your processor calls is healthy.

Local 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.app

What to mock#

ThingRecommended test
Processor business logicCall your function directly with normal objects.
Job methodsMock only the methods you call: updateProgress, log, getChildrenValues.
Queue producer codeAssert add/addBulk was called with the expected name, data, and opts.
Webhook routeSmoke test /api/nextmq/health in a deployed environment.
Full queue lifecycleUse 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 RateLimitError with 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.