Skip to content

Test Patterns

Common patterns for testing taskora tasks effectively.

Testing Retry Behavior

ts
import { describe, it, expect, afterEach } from "vitest"
import { createTestRunner } from "taskora/test"

const runner = createTestRunner()
afterEach(() => runner.clear())

it("retries on transient errors", async () => {
  let callCount = 0
  const flakyTask = runner.app.task("flaky", {
    retry: { attempts: 3, backoff: "fixed", delay: 100 },
    handler: async () => {
      callCount++
      if (callCount < 3) throw new Error("Transient failure")
      return "success"
    },
  })

  const result = await runner.execute(flakyTask, {})
  expect(result.state).toBe("completed")
  expect(result.attempts).toBe(3)
  expect(result.result).toBe("success")
})

Testing with from: taskora

Patch all tasks from a production instance to test inter-task interactions:

ts
import { taskora, processOrderTask, sendConfirmationTask } from "../src/tasks"

const runner = createTestRunner({ from: taskora })
afterEach(() => runner.dispose())

it("processes order and sends confirmation", async () => {
  // processOrder dispatches sendConfirmation internally
  const result = await runner.execute(processOrderTask, {
    orderId: "123",
    items: [{ sku: "ABC", quantity: 1 }],
  })

  expect(result.state).toBe("completed")
  // The confirmation email was also dispatched and processed in-memory
})

Testing Middleware

ts
it("middleware transforms data", async () => {
  const logs: string[] = []

  const mwTestTask = runner.app.task("mw-test", {
    middleware: [
      async (ctx, next) => {
        logs.push("before")
        await next()
        logs.push("after")
      },
    ],
    handler: async (data: string) => {
      logs.push("handler")
      return data.toUpperCase()
    },
  })

  const result = await runner.run(mwTestTask, "hello")
  expect(result).toBe("HELLO")
  expect(logs).toEqual(["before", "handler", "after"])
})

Testing Progress and Logs

ts
it("reports progress and logs", async () => {
  const progressTask = runner.app.task("progress-task", async (data: {}, ctx) => {
    ctx.progress(25)
    ctx.log.info("Quarter done")
    ctx.progress(100)
    ctx.log.info("Complete")
    return "done"
  })

  const result = await runner.execute(progressTask, {})
  expect(result.progress).toBe(100)
  expect(result.logs).toHaveLength(2)
  expect(result.logs[0].message).toBe("Quarter done")
})

Testing Error Cases

ts
it("fails permanently after max attempts", async () => {
  const alwaysFailsTask = runner.app.task("always-fails", {
    retry: { attempts: 2 },
    handler: async () => {
      throw new Error("Always fails")
    },
  })

  const result = await runner.execute(alwaysFailsTask, {})
  expect(result.state).toBe("failed")
  expect(result.attempts).toBe(2)
  expect(result.error).toContain("Always fails")
})

Importing Production Tasks

Selectively import tasks from an existing instance:

ts
const runner = createTestRunner()

runner.importTask(sendEmailTask)
runner.importTask(processImageTask)

// Only these two tasks are available in the runner
const result = await runner.execute(sendEmailTask, data)

Released under the MIT License.