How to Avoid os.Exit(1) in init() During go test in Go
Overview
While building an AWS Lambda Go project with SAM, I wrote the logic that retrieves values from Parameter Store inside init().
The reason was to cache the result of retrieving values from Parameter Store and reuse it, thereby saving on the execution cost of consecutive Lambda invocations.
For details, see the AWS Lambda best practices.
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/best-practices.html
Take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside of the function handler, and cache static assets locally in the /tmp directory. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves execution time and cost.
I wanted to cache the logic that retrieves secrets from Parameter Store in init() and save cost, as shown below.
1 | func init() { |
Regarding the code above:
I run go test on GitHub Actions for testing, butlog.Fatal triggers os.Exit(1) and the process halts.
The error says that no credentials are configured.
1 | NoCredentialProviders: no valid providers in chain. Deprecated. |
Even if I set dummy credentials in GitHub Actions, it still fails.
1 | run: go test -v -count=1 -race -cover -coverprofile=coverage ./... |
If you give up on caching in init() and handle it in the handler instead, the handling is simple.
But “saving cost” just won’t leave my mind. I’m a weak person.
Can’t I avoid this os.Exit(1) at least only when running go test? With that in mind, I gave it a try.
Verification
Suppose we have a main.go file like the following.
Let’s verify how to avoid the log.Fatal in init() when running go test.
1 | var n = 0 |
If you run go test without any special handling, log.Fatal inside init() triggers os.Exit(1) and the process is forcibly terminated.
The test cannot complete.
It would be nice if we could set var n = 1 only during test execution, but…
Verifying whether main_test.go can override the behavior in main.go
To begin with, what is the order of execution? Let me verify that first.
Reference: https://github.com/kenzo0107/go-sample-order
1 | package pkg |
1 |
|
1 | func init() { |
Let’s run go test.
The order of execution was as follows.
- pkg.var
- pkg.init
- main.var
- main.init
- test.init
- test.setup
From this, no processing in main_test.go can run before main.init.
Overriding the variable var n in main.go from main_test.go seems difficult.
Controlling it with an environment variable only during test execution
1 | func init() { |
Replace log.Fatal with a function called logFatal that performs the following behavior.
- The environment variable TEST exists → log a message with
log.Printlnand do NOT executeos.Exit(1) - The environment variable TEST does not exist → execute
os.Exit(1)and forcibly stop
Run main.go.
1 | $ go run main.go |
Set the environment variable TEST=1 and run go test.
1 | $ TEST=1 go test -v . |
We successfully avoided os.Exit(1) inside init(), and the process continued to run.
The GitHub Actions configuration is also a simple setup.
1 | - name: Test |
You could also fiddle with os.Args instead of an environment variable to decide whether go test is running, but the environment variable approach is simple to implement and can be reused for other purposes, so I’d say it’s LGTM.
Summary
Perhaps you shouldn’t do error handling in init() in the first place.
As mentioned in the link below, having the logic in init() fail and then not running the subsequent main() logic doesn’t seem like a bad approach.
That said, manipulating behavior with an environment variable as I did here does feel a bit lacking in elegance.
I hope you’ll keep in mind, somewhere in your heart, that this kind of approach exists for when there are unavoidable circumstances. With that, I’ll lay down my pen here.
Thank you for reading.
Postscript
In the official AWS documentation, the error in init() is just swallowed!
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html
I wonder how they intend to test the case where an error occurs?
Maybe they’ll just tell me “an error will absolutely never happen!”

