Testing

Quill has first-class support for testing. No frameworks to install, no configuration to write. Just use test and expect.

Test blocks

A test block groups related assertions under a descriptive name:

test "basic math":
  expect 2 + 2 is 4
  expect 10 - 3 is 7
  expect 3 * 4 is 12

When you run this, you will see:

  ✓ basic math

1 passed, 0 failed

If a test fails:

  ✗ basic math: Expected (2 + 2) === 5 to be true

0 passed, 1 failed

The expect keyword

expect takes an expression that should evaluate to true. If the expression is false, the test fails with an error message.

-- Equality
expect name is "Alice"

-- Comparisons
expect age is greater than 18
expect count is less than 100

-- Membership
expect colors contains "red"

-- Function results
expect length("hello") is 5
expect upper("hello") is "HELLO"

Testing functions

Define your functions, then write tests for them:

to add a b:
  give back a + b

to multiply a b:
  give back a * b

to isEven n:
  give back n % 2 is 0

test "addition":
  expect add(2, 3) is 5
  expect add(-1, 1) is 0
  expect add(0, 0) is 0

test "multiplication":
  expect multiply(3, 4) is 12
  expect multiply(0, 100) is 0
  expect multiply(-2, 3) is -6

test "even numbers":
  expect isEven(4)
  expect not isEven(3)

Running tests

There are two ways to run tests:

Option 1: quill run

Run your test file like any Quill program. This runs all code in the file, including non-test code:

quill run tests.quill

Option 2: quill test (recommended)

Use the dedicated test command that only runs test blocks and skips everything else. This is the best way to run tests because it gives you a clean summary:

quill test tests.quill

You can also test all files in a directory:

# Test a specific file
quill test math_test.quill

# Test all _test.quill files in the current directory
quill test

# Test all files matching a pattern
quill test tests/

Example output:

  ✓ addition
  ✓ multiplication
  ✓ even numbers

3 passed, 0 failed

Watch Mode

Use --watch (or -w) to automatically re-run your tests every time you save a file:

quill test --watch
quill test math.quill -w

This is perfect for test-driven development — write a test, save, and instantly see if it passes.

Async tests

If your code uses await (for example, fetching data from an API or reading a file), your tests can use await too. Just write await inside the test block as you normally would:

to fetchUser id:
  response is await fetch("https://api.example.com/users/{id}")
  give back await response.json()

test "fetch user returns valid data":
  user is await fetchUser(1)
  expect user.name is not nothing
  expect user.id is 1

test "fetch user handles errors":
  try:
    user is await fetchUser(-1)
    expect user is nothing
  if it fails error:
    expect error is not nothing

Best practices

Organizing tests

As your project grows, keep your tests organized so they are easy to find and maintain:

Group related tests by feature

Give each test file a clear name that matches the file it tests:

-- File structure:
-- math.quill           (your code)
-- math_test.quill      (tests for math.quill)
-- strings.quill        (your code)
-- strings_test.quill   (tests for strings.quill)

Use descriptive test names

A good test name describes the scenario and expected result:

-- Bad: vague names
test "test 1":
  expect add(2, 3) is 5

-- Good: descriptive names
test "add returns the sum of two positive numbers":
  expect add(2, 3) is 5

test "add handles negative numbers":
  expect add(-1, -2) is -3

test "add with zero returns the other number":
  expect add(0, 5) is 5
  expect add(5, 0) is 5

Test one thing at a time

Each test block should focus on a single behavior. If a test fails, you want to know exactly what broke:

-- Good: separate tests for separate behaviors
test "sort puts numbers in ascending order":
  expect sort([3, 1, 2]) is [1, 2, 3]

test "sort handles empty list":
  expect sort([]) is []

test "sort handles single item":
  expect sort([42]) is [42]

A complete test file

-- string_test.quill

test "upper and lower":
  expect upper("hello") is "HELLO"
  expect lower("HELLO") is "hello"

test "trim":
  expect trim("  hi  ") is "hi"

test "startsWith and endsWith":
  expect startsWith("hello", "he")
  expect endsWith("hello", "lo")
  expect not startsWith("hello", "lo")

test "replace":
  expect replace_text("hello world", "world", "Quill") is "hello Quill"

test "split and join":
  words are split("a,b,c", ",")
  expect length(words) is 3
  expect join(words, "-") is "a-b-c"
Try it out! You can run test blocks in the Playground -- select the "Testing" example to see tests in action.