The goal is to test logic on database. It seems that dockertest might be useful for it and they provide examples for how to setup the test. However, these ephemeral databases are empty (no migration done).
How to migrate database when using dockertest?
Not sure that's the best approach but I've connected a few examples and came up with a full migration via [golang-migrate] lib on test setup.
Below the TestMain
is setup func that sets Dockertest with postgres DB connection. Just before m.Run()
, that runs tests, there's runMigrations
which uses golang-migrate to pull all scripts in local dir and apply them to db. It works but it's rather slow.
func TestMain(m *testing.M) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// pulls an image, creates a container based on it and runs it
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "postgres",
Tag: "11",
Env: []string{
"POSTGRES_PASSWORD=secret",
"POSTGRES_USER=user_name",
"POSTGRES_DB=dbname",
"listen_addresses = '*'",
},
}, func(config *docker.HostConfig) {
// set AutoRemove to true so that stopped container goes away by itself
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
})
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}
hostAndPort := resource.GetHostPort("5432/tcp")
databaseUrl := fmt.Sprintf("postgres://user_name:secret@%s/dbname?sslmode=disable", hostAndPort)
log.Println("Connecting to database on url: ", databaseUrl)
resource.Expire(120) // Tell docker to hard kill the container in 120 seconds
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
pool.MaxWait = 120 * time.Second
if err = pool.Retry(func() error {
db, err = sql.Open("postgres", databaseUrl)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// Migrating DB
if err := runMigrations("../migrations", db); err != nil {
log.Fatalf("Could not migrate db: %s", err)
}
//Run tests
code := m.Run()
// You can't defer this because os.Exit doesn't care for defer
if err := pool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
os.Exit(code)
}
func runMigrations(migrationsPath string, db *sql.DB) error {
if migrationsPath == "" {
return errors.New("missing migrations path")
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return err
}
m, err := migrate.NewWithDatabaseInstance("file://"+migrationsPath, "postgres", driver)
if err != nil {
return err
}
err = m.Up()
if err != nil && err != migrate.ErrNoChange {
return err
}
return nil
}