I am trying to mock the Go function os.GetEnv()
in my test files so that I can get the desired value for a particular environment variable.
For example I have defined.
abc := os.GetEnv("XYZ_URL")
Here I should be able to get the needed value for the variable abc
. Also I have several places with the GetEnv
functions.
It will be really helpful if someone can give me a workaround without the help of any Go framework.
First, you can't mock that function. You can only mock something exposed as an interface.
Second, you probably don't need to. Mocks, broadly speaking, are over-used, and should be avoided whenever possible.
When testing environment variables, you have few options.
If you're using Go 1.17 or newer, you can take advantage of the new Setenv
function, which sets the environment variable for the duration of the current test only:
func TestFoo(t *testing.T) {
t.Setenv("XYZ_URL", "http://example.com")
/* do your tests here */
}
For older versions of Go, consider these options:
type OS interface {
Getenv(string) string
}
type defaultOS struct{}
func (defaultOS) Getenv(key string) string {
return os.Getenv(key)
}
// Then in your code, replace `os.Getenv()` with:
myos := defaultOS{}
value := myos.Getenv("XYZ_URL")
And in your tests, create a custom implementation that satisfies the interface, but provides the values you need for testing.
This approach is useful for some things (like wrapping the time
package), but is probably a bad approach for os.Getenv
.
os.Getenv
, and instead just pass the value in. Example, instead of:func connect() (*DB, error) {
db, err := sql.Connect(os.Getenv("XYZ_URL"), ...)
/* ... */
return db, err
}
use:
func connect(url string) (*DB, error) {
db, err := sql.Connect(url, ...)
/* ... */
return db, err
}
In a sense, this only "moves" the problem--you may still want to test the caller, which uses os.Getenv()
, but you can at least reduce the surface area of your API that depends on this method, which makes the third approach easier.
func TestFoo(t *testing.T) {
orig := os.Getenv("XYZ_URL")
os.Setenv("XYZ_URL", "http://example.com")
t.Cleanup(func() { os.Setenv("XYZ_URL", orig) })
/* do your tests here */
}
This approach does have limitations. In particular, it won't work to run multiple of these tests in parallel, so you still want to minimize the number of these tests you run.
This means that approaches 2 and 3 in conjunction with each other can be very powerful.
var getenv = os.Getenv
/* ... then in your code ... */
func foo() {
value := getenv("XYZ_URL") // Instead of calling os.Getenv directly
}
and in a test:
func TestFoo(t *testing.T) {
getenv = func(string) string { return "http://example.com/" }
/* ... your actual tests ... */
}
This has many of the same limitations as option #3, in that you cannot run multiple tests in parallel, as they will conflict.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With