godotenv
godotenv copied to clipboard
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)
}
I can't say I've seen that particular error before. I just tried to replicate by modifying some one of the tests for the package to trace the cwd on my laptop and I couldn't.
I injected
cwd, err := os.Getwd()
t.Log(err)
t.Log(cwd)
to then run tests and get
~/Projects/go/src/github.com/joho/godotenv master ● go test ./... -v
=== RUN TestReadPlainEnv
--- PASS: TestReadPlainEnv (0.00s)
godotenv_test.go:74: <nil>
godotenv_test.go:75: /Users/joho/Projects/go/src/github.com/joho/godotenv
=== RUN TestParse
--- PASS: TestParse (0.00s)
What OS are you running (I just did this on my macbook)? I've got to admit I've no idea why your tests are running in tmp but happy to work with you to troubleshoot.
Thanks for the response, I'm using a Macbook with OS X 10.9.5, and another colleague of mine has also run into this same issue when they tried using https://github.com/subosito/gotenv. Since you've confirmed that this is not expected behaviour, it must be something strange with the way our tests are being run, so I'll investigate further to figure out why this is happening on our end. Thanks again
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?
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.
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:
- Use the absolute path for the
.env
file, as in:err := godotenv.Load(os.ExpandEnv("$GOPATH/src/github.com/adamcohen/godotenv-test/.env"))
- 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.
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.
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
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 ./...
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")
}
}
Thanks @DanielOchoa I didn't know relative path args existed!
This was failing for me too.
@adamcohen 's first solution worked. Thanks.
I would suggest to add some info about this behaviour in the readme, or at least link to this issue.
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 to @rynop
I do something like this :
package main
import (
"fmt"
"github.com/joho/godotenv"
)
const projectDirName = "my-cool-project"
var errEnv = godotenv.Load(".env")
func main() {
if errEnv != nil {
panic(errEnv)
}
fmt.Println("hello")
}
package main
import (
"errors"
"os"
"regexp"
"testing"
"github.com/joho/godotenv"
)
func Test_main(t *testing.T) {
t.Run("SUCCESS_MAIN", func(t *testing.T) {
re := regexp.MustCompile(`^(.*` + projectDirName + `)`)
cwd, _ := os.Getwd()
rootPath := re.Find([]byte(cwd))
errEnv = godotenv.Load(string(rootPath) + `/.env`)
main()
})
t.Run("PANIC_MAIN", func(t *testing.T) {
errEnv = errors.New("error")
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
main()
})
}
Thanks, guys I am new to Go, and this helped me a lot.
Is there a known problem with godotenv.Load() in different namespaces ? somehow my main.go file can find the .env file (placed in root), but the base.go file(namespace models) can't find it, im sure im doing something wrong but i can't point out what exactly. So for better understanding:
-root - main.go - .env - models - base.go
package models
func init() { // TODO: Refactor to errorHandler and integrate Logging e := godotenv.Load() if e != nil { log.Fatal("Error loading .env file on models") } }
Is there a known problem with godotenv.Load() in different namespaces ? somehow my main.go file can find the .env file (placed in root), but the base.go file(namespace models) can't find it, im sure im doing something wrong but i can't point out what exactly. So for better understanding:
-root
- main.go
- .env
- models
- base.go
package models func init() { // TODO: Refactor to errorHandler and integrate Logging e := godotenv.Load() if e != nil { log.Fatal("Error loading .env file on models") } }
Or is it possible that i have a problem because I user godotenv.Load() in the main.go and in the base.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"))
Thanks @DanielOchoa I didn't know relative path args existed!
Does not work with both test and running the application
This is my workaround, it searches upward recursively from the working directory until it finds the file
import (
"fmt"
"os"
"path"
"github.com/joho/godotenv"
)
func searchup(dir string, filename string) string {
if dir == "/" || dir == "" {
return ""
}
if _, err := os.Stat(path.Join(dir, filename)); err == nil {
return path.Join(dir, filename)
}
return searchup(path.Dir(dir), filename)
}
func SetEnviroment(env string) error {
directory, err := os.Getwd()
if err != nil {
return err
}
filename := fmt.Sprintf(".env.%s", env)
filepath := searchup(directory, filename)
if filepath == "" {
return fmt.Errorf("could not find dot env file: %s", filename)
}
return godotenv.Load(filepath)
}
somewhere in my test suite
require.NoError(t, utils.SetEnviroment("test")) // load .env.test
somewhere in my app
if os.Getenv("ENV") {
utils.SetEnviroment(os.Getenv("ENV")) // loads .env.{ENV}
}
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)
I hope this function helps you
func GetEnvValue(key string) string {
path, _ := os.Getwd()
err := godotenv.Load(strings.Split(path, "YOUR_MAIN_PROJECT_NAME")[0] + "YOUR_MAIN_PROJECT_NAME/" + ".env")
if err != nil {
log.Fatalf("Error loading .env file")
}
return os.Getenv(key)
}
Or if you have two .env and .env.test files
func GetEnvValue(key string) string {
if flag.Lookup("test.v") == nil {
// normal run
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("Error loading .env file")
}
return os.Getenv(key)
} else {
// run under go test
path, _ := os.Getwd()
err := godotenv.Load(strings.Split(path, "YOUR_MAIN_PROJECT_NAME")[0] + "YOUR_MAIN_PROJECT_NAME/" + ".env.test")
if err != nil {
log.Fatalf("Error loading .env.test file")
}
return os.Getenv(key)
}
}
use environment variable to specify .env file direcotry.
package configs
func LoadEnv() {
envRoot := os.Getenv("ENV_ROOT")
if "" == envRoot {
envRoot = "./"
}
env := os.Getenv("ENV")
if "" == env {
env = "dev"
}
err := godotenv.Load(envRoot + ".env." + env + ".local")
log.Print(err)
if "test" != env {
err = godotenv.Load(envRoot + ".env.local")
log.Print(err)
}
err = godotenv.Load(envRoot + ".env." + env)
log.Print(err)
err = godotenv.Load(envRoot + ".env") // The Original .env
log.Print(err)
}
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 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)
}
This is terrible! I just spent hours and hours trying to work around this in several ways. As @joho mentioned it's a documented behaviour in golang
, but IMO it's a terrible error, so I would said "a well documented error" 😂
I think the cleaner way to do it at the moment is to use the CLI godotenv -f ...
as was suggested by @kswope, I used that and also ended up relaying on GOMOD
environment variable because this behaviour also impacts other input files I have (e. g. SQLite database for my test and staging environments). And TBH, I don't really know if I should rely on GOMOD
as it seems not to be documented. Additionally, I needed to adjust my GitHub Actions CI Pipeline and VSCode to run the tests properly.
I would like to add that I am using following convention for .env
files across my environments:
- Development:
.env
- Testing:
test.env
- Staging:
staging.env
- Production:
prod.env
So, whenever ENVIRONMENT
is not set I assume it's Development environment (.env
). And for instance my Dockerfile
looks like following:
FROM golang:1.20.4
ENV GOMOD=/api/go.mod
WORKDIR /api
COPY . .
RUN go install github.com/joho/godotenv/cmd/godotenv@latest
RUN go mod tidy
RUN ENVIRONMENT=test godotenv -f "${ENVIRONMENT}.env" go test -v ./...
CMD godotenv -f ${ENVIRONMENT}.env go run main.go
My conclusion is that this is not an error from godetenv
module/library, but a wrong behaviour from this "modern" language itself (cof cof... Google common... please...)
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.
My guess is that when running go test your_test
it loads a different environment than the original one ran by go run main.go
... So the .env
at your project's root level won't be "seen" by the test command, as it is out of scope of the test package. The best and simplest solution to this issue would be to just follow @Shaheer25 advice and copy-paste the .env file into your test package.
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 end up loading .env
file recursively towards parent folder
lastCwd := ""
for {
cwd, err := os.Getwd()
if err != nil {
break
}
if cwd == lastCwd {
// reached root dir
break
}
err = godotenv.Load()
if err == nil {
// success
break
}
if os.Chdir("..") != nil {
break
}
lastCwd = cwd
}
use absolute path like this
envPath := "your directory/.env" // setting with your absolute path
if err := godotenv.Load(envPath); err != nil {
log.Fatalf("Error loading .env file: %v", err)
}