godotenv: Can't use godotenv when running tests

I’ve added godotenv to my project and it works fine when running the compiled binary, but it fails when running my tests:

$ go test ./...
2017/10/13 16:22:37 Error loading .env fileopen .env: no such file or directory

This is happening because the tests are being run in the following temp dir location:

/var/folders/yh/7x8fqv696sb1673313nk8_m80000gn/T/go-build041463486/github.com/myrepo/identity/server/_test

and it can’t find the .env file. Can anyone tell me how to fix this issue? Do I need to specify the absolute path to the .env file? I’m using the following code to load the .env file without specifying a path:

err = godotenv.Load()

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

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 52
  • Comments: 28 (3 by maintainers)

Commits related to this issue

Most upvoted comments

This problem gets even more tricky with Go Modules as your code can (and IMO should) live outside of $GOPATH. Therefore the $GOPATH relative tricks above do not work.

My .env is in the root of my repo, below is a simple util file that finds your repo dir from cwd and loads the .env explicitly.

FWIW I run my tests like: go test ./internal/... -cover

testutils.go:

package testutils

import (
	"os"
	"regexp"

	"github.com/joho/godotenv"
	log "github.com/sirupsen/logrus"
)

const projectDirName = "my-cool-project"

// LoadEnv loads env vars from .env
func LoadEnv() {
	re := regexp.MustCompile(`^(.*` + projectDirName + `)`)
	cwd, _ := os.Getwd()
	rootPath := re.Find([]byte(cwd))

	err := godotenv.Load(string(rootPath) + `/.env`)
	if err != nil {
		log.WithFields(log.Fields{
			"cause": err,
			"cwd":   cwd,
		}).Fatal("Problem loading .env file")

		os.Exit(-1)
	}
}

I just do a simple call from my test setup call:

func TestMain(m *testing.M) {
	testutils.LoadEnv()
        ...
	os.Exit(m.Run())
}

Hope this helps someone else

Thanks for the response @joho. I’m not actually trying to vary the .env based on dev/test (yet), I’m just trying to get godotdenv working in both my compiled app as well as in test mode.

It seems to be failing because during test mode, the tests are not looking for .env in the root of the project, but instead they’re looking for the file relative to where the test is being run. The .env file is in currently in $GOPATH/src/github.com/adamcohen/godotenv-test/.env, but the test is looking for the file in $GOPATH/src/github.com/adamcohen/godotenv-test/loader/.env which doesn’t exist.

So it seems that there are (at least) two ways to solve this:

  1. Use the absolute path for the .env file, as in:
    err := godotenv.Load(os.ExpandEnv("$GOPATH/src/github.com/adamcohen/godotenv-test/.env"))
    
  2. Place the same .env file (or a symlink to it) in all subdirs

Have I understood this correctly? If this is indeed the situation, it might be worth making note of this in the readme file to explain that if you want to write tests for an application which is using godotenv, you’ll need one of the above approaches. I can make a PR for this addition if you’d like.

Relative path args also work for godotenv.Load().

import "github.com/joho/godotenv"

func TestSendMessage(t *testing.T) {

	if err := godotenv.Load("../.env"); err != nil {
		t.Error("Error loading .env file")
	}
}

Why not use the godotenv command like

godotenv go test ./...

An extra benefit is that you can specify a test .env file.

godotenv -f .env.test go test ./...

Or even better you can just overwrite some env variables by using multiple files

godotenv -f .env,.env.test go test ./...

Thanks @DanielOchoa I didn’t know relative path args existed!

Here’s the small snippet, to support different environments file dynamically. Based on the @adamcohen comment.(option 1)

Create a config struct, to hold your environment values and to initiate the struct create a method similar to the one below.(In config.go)


type Config struct {
	AppURL    *url.URL
}

func ReadEnv() *Config {
	currentEnvironment, ok := os.LookupEnv("ENVIRONMENT")

	var err error
	if ok == true {
		err = godotenv.Load(os.ExpandEnv("$GOPATH/src/github.com/<org_name>/<project-name>/.env."+currentEnvironment))
	} else {
		err = godotenv.Load()
	}

	if err != nil {
		panic(err)
	}

       // To load the config values
       appURL, _ := os.LookupEnv("APP_URL")
	config := &Config{
            AppURL: appURL,
       }
	return config
}

In your go files, you can access the env values as below

cfg := &config.ReadEnv()
fmt.Println(cfg.AppURL)

When you run without setting any ENVIRONMENT, it will load the .env file.

If you want to run the tests with values in .env.test, you can run using the below command from any subFolder(by appending the ENVIRONMENT=test before in your test command)

Example:

$ ENVIRONMENT=test go test -run Test

I’m using it like below:

const defaultEnvPath = "/envs/.env"

func LoadEnv() {
	_, f, _, ok := runtime.Caller(0)
	if !ok {
		log.Fatal("Error generating env dir")
	}
	dir := filepath.Join(filepath.Dir(f), "../..", defaultEnvPath)

	err := godotenv.Load(dir)
	if err != nil {
		log.Fatal("Error loading .env file")
	}
}

I’ve created a new empty project, and the tests are no longer run in a temporary dir, so that part has been sorted out, however, I was still unable to get godotenv to work. I’ve put the project here, and this is what I get when I run go test ./...:

(21:49:%)   go test ./...
?   	github.com/adamcohen/godotenv-test	[no test files]
CWD:  /Users/adam/golang/src/github.com/adamcohen/godotenv-test/loader
2017/10/16 21:49:57 Error loading .env file in path: open .env: no such file or directory
FAIL	github.com/adamcohen/godotenv-test/loader	0.005s

So it seems that godotenv is failing because the call to godotenv.Load() happens while in the godotenv-test/loader/ dir, yet the .env file is in the root at godotenv-test. Is this expected behaviour?

Right. Apparently this is documented behaviour (see https://go-review.googlesource.com/c/go/+/17949) but I was never aware of it. I’ll have a think of the best way to document (and/or a recommended work around) and get something into the readme.

This solution on SO https://stackoverflow.com/a/38644571 worked for me and seems quite straightforward

package mypackage

import (
    "path/filepath"
    "runtime"
    "fmt"
)

var (
    _, b, _, _ = runtime.Caller(0)
    basepath   = filepath.Dir(b)
)

func PrintMyPath() {
    fmt.Println(basepath)
}

It gives you the directory of the current file, and from there you can navigate to the .env file you want. I had this directory structure

root
  - testhelpers
    - db   <--- contained the file that needed to load a .env
  .env
  .env.test
  main.go

and in the end my version of their solution looked like this

	_, file, _, ok := runtime.Caller(0)
	if !ok {
		fmt.Fprintf(os.Stderr, "Unable to identify current directory (needed to load .env.test)")
		os.Exit(1)
	}
	basepath := filepath.Dir(file)
	err := godotenv.Load(filepath.Join(basepath, "../.env.test"))

That one definitely is the expected behaviour, the dotenv file should be found at the execution root.

If you’re hoping to vary .env based on dev/test, you’ll need to manually specify the file you want to load (relative to where you’re executing). In the rails world (where the original library this duplicates comes from) you would have .env.development and .env.test which are automatically loaded based on the RAILS_ENV env var, but unfortunately there’s no equivalent to hook off within go.

This solution on SO https://stackoverflow.com/a/38644571 worked for me and seems quite straightforward

package mypackage

import (
    "path/filepath"
    "runtime"
    "fmt"
)

var (
    _, b, _, _ = runtime.Caller(0)
    basepath   = filepath.Dir(b)
)

func PrintMyPath() {
    fmt.Println(basepath)
}

It gives you the directory of the current file, and from there you can navigate to the .env file you want. I had this directory structure

root
  - testhelpers
    - db   <--- contained the file that needed to load a .env
  .env
  .env.test
  main.go

and in the end my version of their solution looked like this

	_, file, _, ok := runtime.Caller(0)
	if !ok {
		fmt.Fprintf(os.Stderr, "Unable to identify current directory (needed to load .env.test)")
		os.Exit(1)
	}
	basepath := filepath.Dir(file)
	err := godotenv.Load(filepath.Join(basepath, "../.env.test"))

Iam new into golang, this fixed my issue. Thanks

I’ve added godotenv to my project and it works fine when running the compiled binary, but it fails when running my tests:

$ go test ./...
2017/10/13 16:22:37 Error loading .env fileopen .env: no such file or directory

This is happening because the tests are being run in the following temp dir location:

/var/folders/yh/7x8fqv696sb1673313nk8_m80000gn/T/go-build041463486/github.com/myrepo/identity/server/_test

and it can’t find the .env file. Can anyone tell me how to fix this issue? Do I need to specify the absolute path to the .env file? I’m using the following code to load the .env file without specifying a path:

err = godotenv.Load()

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

Copy your .env file which is in root directory, and Paste it in your test directory. It worked for me.

I place my env files at the same dir as my go.mod. As every Go project must have a go.mod, I could implement like this. The envFile expects something like “.env” or “.env.test”, etc.


// Load loads the environment variables from the .env file.
func Load(envFile string) {
	err := godotenv.Load(dir(envFile))
	if err != nil {
		panic(fmt.Errorf("Error loading .env file: %w", err))
	}
}

// dir returns the absolute path of the given environment file (envFile) in the Go module's
// root directory. It searches for the 'go.mod' file from the current working directory upwards
// and appends the envFile to the directory containing 'go.mod'.
// It panics if it fails to find the 'go.mod' file.
func dir(envFile string) string {
	currentDir, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	for {
		goModPath := filepath.Join(currentDir, "go.mod")
		if _, err := os.Stat(goModPath); err == nil {
			break
		}

		parent := filepath.Dir(currentDir)
		if parent == currentDir {
			panic(fmt.Errorf("go.mod not found"))
		}
		currentDir = parent
	}

	return filepath.Join(currentDir, envFile)
}

I ended up with using an env for it… I found providing config path explicitly is better, of course there should be default value.

envFile := os.Getenv("ENV_FILE")
if envFile == "" {
    envFile = ".env"
}
err := godotenv.Load(envFile)