# Write tests

Tests in Jadpo sit beside the code they test, and each one gets a
PostgreSQL database of its own. Test files end `.test.jadpo`, and a
test's database is made from your migrations and thrown away after it,
so tests never see each other's data and can run in any order. This guide tests a reading list:
an action on its own, then its routes, signed in as different people.

You start from an app where each `Reader` signs in and keeps a list of
`Entry` records, which only they may read and change, served at
`POST /entries`, `GET /entries` and `POST /entries/{entry_id}/finish`.
The action it tests is this one:

`domains/reading/actions/finish_entry.jadpo`:

```jadpo
use reading/entities { Entry, EntryNotFound }
use reading/types { EntryView }

/// Marks a book on the list as read, now.
action finish_entry(entry_id: Entry.id) fails EntryNotFound -> EntryView {
    var entry: Entry = attempt Entry.get(entry_id)
    var finished: Entry = Entry.update(entry) {
        set: {
            finished_at: clock.now
        }
    }
    return EntryView {
        id: finished.id
        title: finished.title
        finished_at: finished.finished_at
    }
}
```

## Make the data a test starts from

A fixture builds data for tests, in a domain's `fixtures/` folder. Only
tests call fixtures, and a fixture may write straight to an entity,
which a test can't:

`domains/reading/fixtures/a_reader.jadpo`:

```jadpo
use reading/entities { Reader }
use reading/failures { ReaderExists }

/// A reader with the address given.
fixture a_reader(email: Reader.email) -> Reader {
    return attempt Reader.create {
        set: {
            email: email
        }
        conflict: {
            Reader.email: ReaderExists
        }
    }
}
```

`domains/reading/fixtures/an_entry.jadpo`:

```jadpo
use reading/entities { Entry, Reader }
use reading/types { EntryTitle }

/// A book on the reader's list, not yet read.
fixture an_entry(reader: Reader, title: EntryTitle) -> Entry {
    return Entry.create {
        set: {
            reader_id: reader.id
            title: title
            finished_at: none
        }
    }
}
```

## Test an action

An action test calls the action directly, as code does. `clock:` fixes
the time the test runs at, so a value worked out from `clock.now` can be
checked exactly:

`domains/reading/actions/finish_entry.test.jadpo`:

```jadpo
use reading/entities { Entry, Reader }
use reading/types { EntryView }
use reading/fixtures { a_reader, an_entry }

test "finishing a book records when" {
    clock: "2099-03-01T09:30:00Z"
    var ann: Reader = a_reader("ann@example.com")
    var entry: Entry = an_entry(ann, "Middlemarch")
    var finished: EntryView = attempt finish_entry(entry.id)
    assert finished.finished_at == "2099-03-01T09:30:00Z"
}
```

- A test's name is a sentence saying what it proves.
- `attempt` passes the action's failures on: a test that hits one fails,
  naming it.
- `assert` takes any condition. `assert finish_entry(id) fails
  EntryNotFound` passes only when the call fails with that failure.

## Test a route

A route test sends a request through the whole HTTP boundary: the input
is decoded, the caller signed in, policy checked and the failure turned
into a status, as for a real client. `as:` makes the request as someone,
without going through sign-in:

`domains/reading/routes/entries.test.jadpo`:

```jadpo
use reading/entities { Entry, Reader }
use reading/types { NewEntry }
use reading/fixtures { a_reader, an_entry }

test "adding a book answers 201" {
    var ann: Reader = a_reader("ann@example.com")
    var response: Response = request {
        method: HttpMethod.post
        path: "/entries"
        as: Principal.user(ann)
        input: NewEntry {
            title: "Middlemarch"
        }
    }
    assert response.status == 201
}

test "the list needs someone signed in" {
    var response: Response = request {
        method: HttpMethod.get
        path: "/entries"
    }
    assert response.status == 401
    assert response.code == "authentication_required"
}

test "nobody finishes another reader's book" {
    var ann: Reader = a_reader("ann@example.com")
    var bo: Reader = a_reader("bo@example.com")
    var entry: Entry = an_entry(ann, "Middlemarch")
    var response: Response = request {
        method: HttpMethod.post
        path: "/entries/{entry_id}/finish"
        params: {
            entry_id: entry.id
        }
        as: Principal.user(bo)
    }
    assert response.status == 404
    var listed: Response = request {
        method: HttpMethod.get
        path: "/entries"
        as: Principal.user(bo)
    }
    assert listed.output == []
}
```

- `request { }` takes the method, the path with its `params:`, the
  caller in `as:`, and the `input:`. Leave out `as:` to call as nobody.
- The `Response` has the `status`, the error's `code`, and the
  `output`.
- The last test proves that policy keeps one reader's list from
  another: Bo gets a `404` for Ann's entry, as if it weren't there.

## Run them

Run `jadpo migrate new initial` and `jadpo api update` if you haven't,
then:

```sh
jadpo test
```

```text
tested: 4 passed, 1 action test, 3 route tests
```

Jadpo works out each test's kind from what it calls, and counts each
kind apart, since an action test doesn't prove the route decodes its
input or checks who is signed in. When a test fails, it shows each value
in the assertion and the command that reruns just that test:

```text
FAIL  finishing a book records when
 --> domains/reading/actions/finish_entry.test.jadpo:10

  assert finished.finished_at == "2099-03-01T09:31:00Z"
         |        |
         |        "2099-03-01T09:30:00Z"
         EntryView { id: "03b506f3-b9c0-71f9-8d76-e4864446acb7", title: "Middlemarch", finished_at: "2099-03-01T09:30:00Z" }

  expected: "2099-03-01T09:31:00Z"
  actual: "2099-03-01T09:30:00Z"

  rule: test.assertion_failed
  rerun: jadpo test domains/reading/actions/finish_entry.test.jadpo:10 --seed 1

test failed: 1 failed, 3 passed, 1 action test, 3 route tests
```

To run fewer tests, name a file, `jadpo test <file>`, or a test,
`jadpo test <file>:<line>`; or use `--name <text>`, `--kind route`, or
`--failed` for those that failed last time. `jadpo test --watch` reruns
the tests a change reaches each time you save.

## Other things tests do

- **Calls to other services** are never made: `fakes:` gives each call
  its answer ([Call an outside API](guide-call-an-outside-api.md)).
- **Emails** are caught, and `email.sent` lists them
  ([Add sign-in](guide-add-sign-in.md)).
- **Schedules** run with `.run()`, and the work they start finishes
  before the next line ([Run background work](guide-run-background-work.md)).
- **`clock.advance(1d)`** moves a test's clock on.

## Read more

[Where tests live](../topics/where-tests-live.md),
[Writing a test](../topics/writing-a-test.md),
[Kinds of test](../topics/kinds-of-test.md),
[Acting as a user](../topics/acting-as-a-user.md),
[Controlling time](../topics/controlling-time.md),
[Shared test data: fixtures](../topics/shared-test-data-fixtures.md),
[Assertions](../topics/assertions.md),
[Running tests](../topics/running-tests.md) and
[The test database](../topics/the-test-database.md).
