Testing with a Database
Some tests need a real database, for example to run commands from end to end.
To get one, use the dbtest package:
Changed in 2.0
import "github.com/thenativeweb/architecturekit-golang/architecturekit/architecturekittest/dbtest"
Call the Store function with a *testing.T, the source, and the schemas to register. It returns a store on a database that all tests of the package share, which the first test that asks for it starts in a container. Options for the store, such as WithStateCache, follow the schemas, so that a test runs with the same store as the application:
func TestAcquireBook(t *testing.T) {
store := dbtest.Store(t, "https://library.eventsourcingdb.io", bookState.Schemas())
_, err := architecturekit.Execute(context.TODO(), store, acquireBook, AcquireBook{
BookID: rand.Text(),
// ...
})
if err != nil {
t.Fatal(err)
}
}
To stop the database once all tests have run, call the Main function from TestMain:
func TestMain(m *testing.M) {
dbtest.Main(m)
}
If TestMain has more to do once the tests have run, such as closing a browser, run the tests yourself, and call the StopSharedDatabase function afterwards. It does nothing if no test has started the database. Here, closeBrowser stands for whatever else is left to do:
func TestMain(m *testing.M) {
code := m.Run()
closeBrowser()
if err := dbtest.StopSharedDatabase(); err != nil {
fmt.Fprintln(os.Stderr, err)
code = 1
}
os.Exit(code)
}
Note that the schemas come as a single slice, so that the options can follow. To hand over the schemas of several states, join them with slices.Concat, for example slices.Concat(bookState.Schemas(), readerState.Schemas()).
The tests share the events as well, so a test writes to subjects of its own, for example with a random ID in them, and reads only from those. A test that reads more than that, such as a projection from /, needs a database of its own. Call the IsolatedStore function to start one for the test alone, which is stopped once the test is over. It takes the same arguments as Store, and a few seconds to start, so use it only where the shared one would not do.
For a test that connects by itself, such as one that starts a whole server, call the SharedDatabase or the IsolatedDatabase function. Each returns a *Database, whose URL and APIToken fields are what a client needs. Its Client function returns a client, for example to write an event that no command would, and its Store function returns a store, as above. Here, server.Config stands for the configuration of your application:
database := dbtest.SharedDatabase(t)
config := server.Config{
DatabaseURL: database.URL.String(),
APIToken: database.APIToken,
}
Note that with -short every test that asks for a database is skipped, so that the other tests run without Docker.
Note that a nil option makes Store, IsolatedStore, and the Store function of a Database panic before they ask for a database, so with -short as well.
Changed in 2.0Note that the database runs the image thenativeweb/eventsourcingdb:latest, as the client SDK starts it, rather than a version of your choice. Docker pulls the image only if it is missing, so which release that is depends on what the machine has pulled before. The database runs without a signing key, so its events carry no signature, and a store with WithSignatureVerification fails to read them with an error of the category ErrUnverified. Signature verification can therefore not be tested with it (see Verifying Events).
Note that dbtest is a package of its own because it starts the database with Testcontainers, which brings along the Docker client. A package whose tests only import architecturekittest, for example to test deciders and projections, builds without either.