# Testing Projections

To test a projection without a database, call the `project` function of the module `architecturekit::testing` with the projection and the stored events, which needs the feature `testing` (see **[Testing Deciders](/docs/architecturekit/rust/testing-deciders)**).

To turn typed events for the same subject into stored ones, call the `stored_events` function, which numbers them from `0`. A projection applies events asynchronously, so a test runs on Tokio, and waits for `project`. The `expect_items` function then expects the view to hold exactly the given items, in the given order. It compares them with `==`, as `assert_eq!` does, so the items implement `PartialEq` and `Debug`, as `BookItem` does (see **[Defining Views](/docs/architecturekit/rust/defining-views)**):

```rust
use architecturekit::testing::{expect_items, project, stored_events};

#[tokio::test]
async fn projects_the_catalog() {
  let catalog_projection = CatalogProjection { catalog: new_catalog() };

  project(
    &catalog_projection,
    &stored_events(
      &Subject::new("/books/42"),
      [
        BookEvent::Acquired(BookAcquired {
          title: "2001 – A Space Odyssey".to_owned(),
          author: "Arthur C. Clarke".to_owned(),
          isbn: "978-0756906788".to_owned(),
        }),
        BookEvent::Borrowed(BookBorrowed {
          borrowed_by: "23".to_owned(),
          borrowed_until: "2026-10-24".to_owned(),
        }),
      ],
    ),
  )
  .await
  .expect("the events are applied");

  expect_items(
    &catalog_projection.catalog,
    [BookItem {
      id: "42".to_owned(),
      title: "2001 – A Space Odyssey".to_owned(),
      author: "Arthur C. Clarke".to_owned(),
      is_borrowed: true,
      borrowed_until: "2026-10-24".to_owned(),
      revision: Some(Revision::from(1)),
    }],
  )
  .await;
}
```

**`project` hands over the events as they are stored, and they take the same way as in a run**: the upcasters of the projection run, an event of a type that the projection has no variant for is left out, an event that an upcaster splits arrives part by part, and the projection learns of every event in `seen`. To test that a projection handles an older event type, hand over an event of that type, made with `StoredEvent::raw` (see **[Testing Upcasters](/docs/architecturekit/rust/testing-deciders#testing-upcasters)**). `project` takes `StoredEvent`s, owned or borrowed, as well as events of the client SDK, such as those read from a database.

If the projection refuses an event, `project` returns the failure, rather than failing the test, so that a test can check that a projection refuses an event. `expect` fails the test with it, as above. The failure names the event by its ID, as in `applying event 1: …`. A panic of the projection is returned the same way, with its message, as in `permanent failure: applying event 1: the projection panicked: …`, rather than taking the test down with it.

To get the items as a `Vec` instead, call the `items_of` function:

```rust
use architecturekit::testing::items_of;

let items = items_of(&catalog_projection.catalog).await;
```

Both fail the test if the view fails while it is read, also after some of the items.

If a projection reads the time of an event, or the events of several subjects have to follow one another, call the `stored_events_at` function instead of `stored_events`. It numbers the events from the given ID on, and times them one minute apart from the given time on, which is a `DateTime<Utc>` of the chrono crate:

```rust
use architecturekit::testing::stored_events_at;
use chrono::{TimeDelta, TimeZone, Utc};

let noon = Utc.with_ymd_and_hms(2026, 3, 2, 12, 0, 0).unwrap();

let events = [
  stored_events_at(&Subject::new("/books/42"), 0, noon, [book_acquired()]),
  stored_events_at(&Subject::new("/books/23"), 1, noon + TimeDelta::minutes(1), [book_acquired()]),
]
.concat();
```

Here, `book_acquired` returns a `BookAcquired` event, as under **[Testing Deciders](/docs/architecturekit/rust/testing-deciders)**. The fields of a `StoredEvent` are public, so that a test can also give an event a type that the projection does not know, data that it can not read, or a time of its own.

How a projection is run depends on the method that starts it, which the compiler checks against the traits the projection implements (see **[Resuming Projections](/docs/architecturekit/rust/projections#resuming-projections)**), so there is nothing to check about it in a test. `project` drives a resumable projection as well, but saves no checkpoint.

To test a transactional projection, call the `project_transactional` function instead of `project`. It begins a transaction, applies the events, and commits the transaction with the ID of the last event. If an event is refused, or the projection panics, it rolls the transaction back, and returns the failure, naming the event as `project` does. Here, `book_table` is a `TransactionalBookTableProjection` (see **[Resuming Projections](/docs/architecturekit/rust/projections#resuming-projections)**):

```rust
use architecturekit::testing::project_transactional;

project_transactional(
  &book_table,
  &stored_events(
    &Subject::new("/books/42"),
    [BookEvent::Acquired(BookAcquired {
      title: "2001 – A Space Odyssey".to_owned(),
      author: "Arthur C. Clarke".to_owned(),
      isbn: "978-0756906788".to_owned(),
    })],
  ),
)
.await
.expect("the events are applied");
```

*Note that `project_transactional` begins no transaction without events, and that a failure of the rollback is reported alongside the failure that caused it.*
