Search code examples
goenvironment-variablesgo-testing

How to use dynamic location for godotenv.Load() .env file?


Problem

I am building a REST API in Go. The godotenv package is used to load the environment variables. Running go run main.go, the project runs the API as expected, the environment variables are loaded.

However, when wanting to run the test using: go test ./... - which runs config/config_test.go among others - it throws the following error: Error loading .env file (as specified in function).

Given the following project structure:

> app
> auth
> config
  - config.go
  - config_test.go
> migrations
> static
> vendor
- .env
- .gitignore
- docker-compose.yml
- go.mod
- go.sum
- main.go
- README.md

In config.go, I use the following function to load the Database configuration.

func GetConfig() *Config {
    err := godotenv.Load(".env")

    if err != nil {
        log.Fatalf("Error loading .env file")
    }

    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")
    dbName := os.Getenv("DB_DATABASE")
    dbUsername := os.Getenv("DB_USERNAME")
    dbPassword := os.Getenv("DB_PASSWORD")

    return &Config{
        DB: &DBConfig{
            Connection: "mysql",
            Host:       dbHost,
            Port:       dbPort,
            Username:   dbUsername,
            Password:   dbPassword,
            Name:       dbName,
            Charset:    "utf8",
        },
    }
}

I understand that it works when running from root, because the .env resides in root. When running config/config_test.go, it tries to look for the .env file in the /config/.env. If I change the line: err := godotenv.Load(".env") to err := godotenv.Load("../.env"), the config_test.go runs successfully, but the go run main.go from root does not run successfully.

Question

How can I load the .env location dynamically from the GetConfig() function in config.go, so that both the go test ./... and go run main.go can load the .env?

Edit

I am aware that passing a path string parameter to the GetConfig() function would work in my application (I am initializing this config in the app package). However, I want to create multiple tests in different directories, and prefer not to pass a parameter. Is there another way to accomplish this?


Solution

  • Following the suggestion of @Inian, I implemented the following solution, also listed on the Issues tab of the godotenv package.

    In config.go I added a constant for the directory name (which is rest-api in my case). I added a loadEnv function that tries to get the root path of the project dynamically, based on the project name and the current working directory.

    const projectDirName = "rest-api" // change to relevant project name
    
    func loadEnv() {
        projectName := regexp.MustCompile(`^(.*` + projectDirName + `)`)
        currentWorkDirectory, _ := os.Getwd()
        rootPath := projectName.Find([]byte(currentWorkDirectory))
    
        err := godotenv.Load(string(rootPath) + `/.env`)
    
        if err != nil {
            log.Fatalf("Error loading .env file")
        }
    }
    
    func GetConfig() *Config {
        loadEnv()
    
        dbHost := os.Getenv("DB_HOST")
        dbPort := os.Getenv("DB_PORT")
        dbName := os.Getenv("DB_DATABASE")
        dbUsername := os.Getenv("DB_USERNAME")
        dbPassword := os.Getenv("DB_PASSWORD")
    
        return &Config{
            DB: &DBConfig{
                Connection: "mysql",
                Host:       dbHost,
                Port:       dbPort,
                Username:   dbUsername,
                Password:   dbPassword,
                Name:       dbName,
                Charset:    "utf8",
            },
        }
    }