Search code examples
unit-testinggo

Golang dependency injection for mock database in unit testing


I am creating a mock database with Golang interfaces to test my handler function. However, I don't seem to be performing the dependency injection correctly in my unit testing environment. I have created a setup_test.go file to set up my mock database environment. In the code snippets below, I will go over the flow of how my dependency injection works.

  1. main.go
package main

type application struct {
    DB repository.DatabaseRepo
}

func main() {
    app := application{}

    conn, err := app.connectToDB()
    if err != nil {
        log.Fatal("Failed to connect to PostgreSQL:", err)
    }

    defer func() {
        conn.Close(context.Background())
    }()

    app.DB = &dbrepo.PostgresDBRepo{DB: conn}

    // Passing app.DB to routes function for DI
    mux := routes.Routes(app.DB)
package repository

type DatabaseRepo interface {
    Connection() *pgx.Conn
    GetCountByUsername(ctx context.Context, username string) (int, error)
}
  1. routes.go
package routes

func Routes(app repository.DatabaseRepo) http.Handler {
    mux := chi.NewRouter()

    signUpH := user.New(app)
    mux.Post("/signup", utils.MakeHTTPHandler(signUpH.SignUp))
}
  1. SignUp.go
    The code fails at this line which is calling the database. I have set up my mock database in point 4 in setup_test.go. This is the error message I am getting "panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation ...]".
package user

type application struct {
    DB repository.DatabaseRepo
}

// Dependency Injection of Repository (Database)
func New(app repository.DatabaseRepo) *application {
    return &application{DB: app}
}

func (app application) SignUp(w http.ResponseWriter, req *http.Request) error {
    // some other logic

    // When I run `go run .`, this prints out to be *dbrepo.PostgresDBRepo
    // When I run `go test -v ./...`, this prints out to be <nil> 
    // but the expected type should be *dbrepo.TestDBRepo
    fmt.Printf("Type of app.DB: %T\n", app.DB)

    // fails on this line in unit testing
    userCount, err := app.DB.GetCountByUsername(ctx, Username)

    // some other logic
}
  1. setup_test.go
package main

var app application

func TestMain(m *testing.M) {

    app.DB = &dbrepo.TestDBRepo{}

    fmt.Printf("Type of app.DB: %T\n", app.DB) // output type is *dbrepo.TestDBRepo

    os.Exit(m.Run())
}

I have seen many unit testing for similar mock database using this approach but they usually do it within the same package main which will not have this issue if you have nested folders and packages like mine.

What can I try next?


Solution

  • (Posted answer on behalf of the question author to move it to the answers section).

    It seems like the only way to solve this issue is to create multiple TestMain in packages that rely on this test environment (in my case, the mock database) as stated in https://groups.google.com/g/golang-nuts/c/SxEkZhWl3QA. Maybe in future this concern will be added into Golang where TestMain can be integrated to different packages.