Docs / ArchitectureKit 2.0 / Guides / Testing Projections

Testing Projections

To test a projection without a database, call the Project function with a *testing.T, the projection, and the stored events.

To turn typed events for the same subject into stored ones, call the StoredEvents function, which numbers them from 0:

func TestCatalogProjection(t *testing.T) {
  catalog := newCatalog()
  catalogProjection := newCatalogProjection(catalog)

  architecturekittest.Project(t, catalogProjection,
    architecturekittest.StoredEvents("/books/42",
      BookAcquired{
        Title:  "2001 – A Space Odyssey",
        Author: "Arthur C. Clarke",
        ISBN:   "978-0756906788",
      },
      BookBorrowed{
        BorrowedBy:    "23",
        BorrowedUntil: "2026-10-24",
      },
    )...,
  )

  architecturekittest.ExpectItems(t, catalog, BookItem{
    ID:            "42",
    Title:         "2001 – A Space Odyssey",
    Author:        "Arthur C. Clarke",
    IsBorrowed:    true,
    BorrowedUntil: "2026-10-24",
    EventID:       "1",
  })
}

Project hands over the events as they are stored, so a projection that uses upcasters runs them, just as it does with a database. To test that a projection handles an older event type, hand over an event of that type.

The ExpectItems function expects the view to hold exactly the given items, in the given order. It compares the items by value, so that items with slices or maps work too.

Note that ExpectItems compares with reflect.DeepEqual, so a nil slice or map is not equal to an empty one.

To get the items as a slice instead, call the ItemsOf function:

items := architecturekittest.ItemsOf(t, catalog)

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 StoredEventsAt function instead of StoredEvents. It numbers the events from the given ID on, and times them one minute apart from the given time on:

noon := time.Date(2026, time.March, 2, 12, 0, 0, 0, time.UTC)

events := append(
  architecturekittest.StoredEventsAt("/books/42", 0, noon, BookAcquired{ /* ... */ }),
  architecturekittest.StoredEventsAt("/books/23", 1, noon.Add(time.Minute), BookAcquired{ /* ... */ })...,
)

To check how a projection will be run, call the ExpectMode function:

architecturekittest.ExpectMode(t, catalogProjection, architecturekit.ModeRebuild)

To test a transactional projection, call the ProjectTransactional 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, it rolls the transaction back:

architecturekittest.ProjectTransactional(t, &TransactionalBookTableProjection{},
  architecturekittest.StoredEvents("/books/42",
    BookAcquired{
      Title:  "2001 – A Space Odyssey",
      Author: "Arthur C. Clarke",
      ISBN:   "978-0756906788",
    },
  )...,
)

Note that Project fails the test for a projection that implements Transactional in addition to Apply.