9

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?

Eric Landheer
  • 2,033
  • 1
  • 11
  • 25
  • Pass the file location into `GetConfig`. Then in `main.go` pass `.env`, and in your test, pass `./testdata/.env` or whatever. – Jonathan Hall Jul 12 '21 at 11:42
  • That is indeed a valid solution and I have considered it, likewise as a simple if statement in case loading fails. It will work in most cases since I have a App module that holds the config as well. However, I am planning on creating tests that will work individually of that initialization. I was hoping for a solution without having to pass parameters or creating if statements. – Eric Landheer Jul 12 '21 at 11:50
  • Why do you not want to rely on passing a parameter? That's the correct solution. I'm also not sure what you mean by 'creating if statements"... what kinds of if statements would be required? – Jonathan Hall Jul 12 '21 at 11:54
  • 2
    There are multiple possible solutions discussed here - https://github.com/joho/godotenv/issues/43 – Inian Jul 12 '21 at 11:56
  • @Flimzy In case the first if statement fails, try another .env location ad infinitum. Not elegant and still the parameter solution is preferable. I rephrased `not wanting to rely` to `prefer not to`, because I believe there should be another solution and will try one of the [suggested solutions](https://github.com/joho/godotenv/issues/43#issuecomment-503183127) provided by @Inian. – Eric Landheer Jul 12 '21 at 12:03
  • @inian provided solution on Github did the trick. I have added it as the answer to my question. – Eric Landheer Jul 12 '21 at 13:02

1 Answers1

11

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",
        },
    }
}
Eric Landheer
  • 2,033
  • 1
  • 11
  • 25