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.
Added in 2.2If the projection refuses an event, Project fails the test and names the event by its ID, as in applying event 1: …. A panic of the projection fails the test the same way, with the value and the stack of the panic, as in applying event 1: panic: …, rather than taking the other tests of the package down with it.
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)
Changed in 2.2To 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, or the projection panics, it rolls the transaction back, and fails the test, naming the event as Project does:
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.