# Testing Deciders

In this step, you **test the deciders** from **[Making Decisions](/learn/tutorials/first-steps-with-architecturekit/making-decisions)** – without a database.

## Given, When, Then

A decider receives a command and a state, and the state is built from events. So every test of a decider can be told in three parts: **given** the events that have happened so far, **when** a command arrives, **then** the decider writes these events, or rejects the command. The **`architecturekittest`** package spells out tests exactly this way, and it builds the state from the given events itself, without EventSourcingDB.

Create a file `deciders_test.go`:

```go
package main

import (
  "testing"

  "github.com/thenativeweb/architecturekit-golang/architecturekit"
  "github.com/thenativeweb/architecturekit-golang/architecturekit/architecturekittest"
)

func TestBorrowBook(t *testing.T) {
  t.Run("lends an available book", func(t *testing.T) {
    architecturekittest.Given(t, borrowBook,
      BookAcquired{
        Title:  "2001 – A Space Odyssey",
        Author: "Arthur C. Clarke",
        ISBN:   "978-0756906788",
      },
    ).
      When(BorrowBook{BookID: "42", ReaderID: "23", BorrowedUntil: "2026-10-24"}).
      ThenEvents(BookBorrowed{BorrowedBy: "23", BorrowedUntil: "2026-10-24"}).
      ThenPreconditions(architecturekittest.OnStateRead())
  })

  t.Run("rejects a book that is already borrowed", func(t *testing.T) {
    architecturekittest.Given(t, borrowBook,
      BookAcquired{
        Title:  "2001 – A Space Odyssey",
        Author: "Arthur C. Clarke",
        ISBN:   "978-0756906788",
      },
      BookBorrowed{BorrowedBy: "23", BorrowedUntil: "2026-10-24"},
    ).
      When(BorrowBook{BookID: "42", ReaderID: "17", BorrowedUntil: "2026-10-31"}).
      ThenRejected("book 42 is already borrowed")
  })

  t.Run("rejects a book that does not exist", func(t *testing.T) {
    architecturekittest.Given(t, borrowBook).
      When(BorrowBook{BookID: "42", ReaderID: "23", BorrowedUntil: "2026-10-24"}).
      ThenFailed(architecturekit.ErrDomain)
  })
}

func TestReturnBook(t *testing.T) {
  t.Run("returns a borrowed book", func(t *testing.T) {
    architecturekittest.Given(t, returnBook,
      BookAcquired{
        Title:  "2001 – A Space Odyssey",
        Author: "Arthur C. Clarke",
        ISBN:   "978-0756906788",
      },
      BookBorrowed{BorrowedBy: "23", BorrowedUntil: "2026-10-24"},
    ).
      When(ReturnBook{BookID: "42"}).
      ThenEvents(BookReturned{})
  })

  t.Run("does nothing for a book that is not borrowed", func(t *testing.T) {
    architecturekittest.Given(t, returnBook,
      BookAcquired{
        Title:  "2001 – A Space Odyssey",
        Author: "Arthur C. Clarke",
        ISBN:   "978-0756906788",
      },
    ).
      When(ReturnBook{BookID: "42"}).
      ThenNothing()
  })
}
```

**`Given`** takes the test, the decider, and the events that have happened so far, and **`When`** takes the command. The functions that start with `Then` check the outcome, and since each of them returns the outcome again, you can chain them:

- **`ThenEvents`** expects exactly the given events, in the given order.
- **`ThenRejected`** expects a rejection with exactly the given message, and **`ThenFailed`** an error of the given category, whatever its message.
- **`ThenNothing`** expects neither events nor an error.
- **`ThenPreconditions`** expects exactly the given preconditions, here the `OnStateRead` precondition from **[Making Decisions](/learn/tutorials/first-steps-with-architecturekit/making-decisions)**.

## Running the Tests

Run the tests. They need neither EventSourcingDB nor the API, so you can stop both first:

```shell
go test -v
```

All five tests pass. The durations vary:

```
=== RUN   TestBorrowBook
=== RUN   TestBorrowBook/lends_an_available_book
=== RUN   TestBorrowBook/rejects_a_book_that_is_already_borrowed
=== RUN   TestBorrowBook/rejects_a_book_that_does_not_exist
--- PASS: TestBorrowBook (0.00s)
    --- PASS: TestBorrowBook/lends_an_available_book (0.00s)
    --- PASS: TestBorrowBook/rejects_a_book_that_is_already_borrowed (0.00s)
    --- PASS: TestBorrowBook/rejects_a_book_that_does_not_exist (0.00s)
=== RUN   TestReturnBook
=== RUN   TestReturnBook/returns_a_borrowed_book
=== RUN   TestReturnBook/does_nothing_for_a_book_that_is_not_borrowed
--- PASS: TestReturnBook (0.00s)
    --- PASS: TestReturnBook/returns_a_borrowed_book (0.00s)
    --- PASS: TestReturnBook/does_nothing_for_a_book_that_is_not_borrowed (0.00s)
PASS
ok  	library	0.322s
```

## When a Test Fails

To see that the tests actually guard the rules, remove the check for a borrowed book from `borrowBook` in `deciders.go`:

```go
if book.IsBorrowed {
  return nil, architecturekit.NewDomainError("book %s is already borrowed", cmd.BookID)
}
```

Run the tests again, without `-v`:

```shell
go test
```

The decider now lends a book that is already borrowed, and the test tells you so. Again, the durations vary:

```
--- FAIL: TestBorrowBook (0.00s)
    --- FAIL: TestBorrowBook/rejects_a_book_that_is_already_borrowed (0.00s)
        deciders_test.go:34: expected rejection "book 42 is already borrowed", got 1 event(s): [io.eventsourcingdb.library.book-borrowed]
FAIL
exit status 1
FAIL	library	0.362s
```

Put the check back, and the tests pass again. For more ways to check an outcome, see **[Testing Deciders](/docs/architecturekit/testing-deciders)**, and for why tests like these matter, see **[Testing Event-Sourced Systems](/concepts/testing-event-sourced-systems)**.

## Shutting Down

If they are still running, press Ctrl+C in the terminals of the API and of EventSourcingDB. Since the data directory of EventSourcingDB is temporary, all events are gone afterwards.

## Next Steps

You have built a small library with ArchitectureKit, from events to a tested HTTP API. To go further:

- **[Testing Projections](/docs/architecturekit/testing-projections)** and **[Testing Queries](/docs/architecturekit/testing-queries)** test the reading side without a database as well.
- **[Using Preconditions](/docs/architecturekit/using-preconditions)** shows how to check the revision a caller has seen, and how to enforce rules across books, such as acquiring every ISBN only once.
- **[Setting Up an HTTP API](/docs/architecturekit/setting-up-an-http-api)** and **[Handling Commands over HTTP](/docs/architecturekit/handling-commands-over-http)** add users, and refuse commands they are not allowed to send.
- **[Versioning Events](/docs/architecturekit/versioning-events)** changes the shape of an event without changing the events that are already stored.
