Testing

Norq includes a declarative test framework. Write assertions in tests.yaml and run them with norq test. Each test case verifies business requirements on compiled output -- subject lines contain the right text, SMS fits in one segment, Slack has action buttons, etc.

File

tests.yaml -- placed inside the notification directory alongside templates.

Test structure

tests:
  - name: "Email has subject"
    channel: email
    sample: "New user"
    assert:
      subject: { contains: "Welcome" }

Each test case specifies:

Field Required Description
name Yes Human-readable test name
channel No Channel to test (defaults to all)
sample No Named sample from data.samples.yaml (defaults to first)
all_channels No true to run across all channels
all_samples No true to run across all samples
assert Yes Map of target -> operator -> expected value

Assertion targets

Target Type Description
subject String Email subject or push title
body String SMS body, Slack fallback text, email plain text
html String Email HTML output
sms_segments Number SMS segment count
payload_bytes Number Serialized payload size in bytes
blocks JSON Slack Block Kit blocks array
diagnostics Number Lint error/warning count

Assertion operators

String operators

assert:
  subject: { eq: "Welcome, Gaurav!" }
  subject: { contains: "Welcome" }
  subject: { not_contains: "error" }
  body: { matches: "order #[A-Z]+-\\d+" }
Operator Description
eq Exact match
contains Substring match
not_contains Substring must not be present
matches Regex match

Number operators

assert:
  sms_segments: { lte: 1 }
  payload_bytes: { lt: 4096 }
  sms_segments: { gte: 1 }
Operator Description
eq Equal
lt Less than
lte Less than or equal
gt Greater than
gte Greater than or equal

Block operators (Slack)

assert:
  blocks: { any: { type: "header" } }
  blocks: { any: { type: "actions" } }
Operator Description
any At least one block matches the partial JSON

Diagnostic operators

assert:
  diagnostics: { errors: 0 }
Operator Description
errors Exact count of lint errors

Examples

Smoke test (all channels render)

tests:
  - name: "All render without errors"
    all_channels: true
    all_samples: true
    assert:
      diagnostics: { errors: 0 }

SMS segment budget

tests:
  - name: "SMS fits 1 segment"
    channel: sms
    sample: "New user"
    assert:
      sms_segments: { lte: 1 }
 
  - name: "SMS fits 2 segments with long data"
    channel: sms
    sample: "Pro user with long name"
    assert:
      sms_segments: { lte: 2 }

Email content checks

tests:
  - name: "Email subject has order ID"
    channel: email
    sample: "Shipped with discount"
    assert:
      subject: { contains: "ORD-" }
 
  - name: "Email has unsubscribe link"
    channel: email
    sample: "New user"
    assert:
      html: { contains: "unsubscribe" }

Slack structure

tests:
  - name: "Slack has primary action"
    channel: slack
    sample: "New user"
    assert:
      blocks: { any: { type: "actions" } }
 
  - name: "Slack has header"
    channel: slack
    sample: "New user"
    assert:
      blocks: { any: { type: "header" } }

Push payload size

tests:
  - name: "Push under 4KB"
    channel: push
    sample: "New user"
    assert:
      payload_bytes: { lte: 4096 }

Running tests

# Test all notifications
norq test
 
# Test one notification
norq test order-shipped
 
# JSON output (for CI)
norq test --json

CI integration

Add to your CI pipeline:

# GitHub Actions
- run: norq lint --json
- run: norq test --json

Lint checks structural correctness (valid schemas, no duplicate channels, etc.). Tests check business requirements (content, size limits, structure).

Relationship to lint

Check type Tool Purpose
Schema valid norq lint Data contract is valid JSON Schema
Templates parse norq lint No syntax errors
Null safety norq lint Nullable fields guarded with :::if
Subject has text norq test Business assertion
SMS fits budget norq test Business assertion
Slack has buttons norq test Business assertion

Use both in CI. Lint catches structural issues; tests catch content/business issues.