35

Is there a way to get the command line arguments in go "tests",
When you call go test obviously your main is not run, so is there a way to process command line arguments,

One way would be to use the flags packages and check for the command line arguments in each test or function being tested, but that is not ideal for that you need to do this in lots and lots of places, unlike the way you to it just in main when you run the application.

One may think it is a wrong thing to do, and that it is against purity of unit-tests:

  1. not all tests are unit tests
  2. it is very functional not to rely on "ENV" variables and actually pass the stuff as arguments in command line,

For the record I ended up putting an init() function in one of my _test files, and set the variable that is set through flags when the main is called this way.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Ali
  • 18,665
  • 21
  • 103
  • 138
  • 1
    I'm not sure what you're asking. Tests shouldn't depend on command line arguments. – Cubic Jan 25 '14 at 14:41
  • 1
    behvaioral tests can, unit test shouldn't , like if something needs to know if it is on development environment or production? – Ali Jan 25 '14 at 14:56
  • 2
    If you absolutely have to you can use the `-args` flag for `go test`. `go test -v -args --test-args foo` – eric chiang Feb 23 '17 at 17:18
  • @Cubic except when you want to test that command line arguments are processed correctly ... – NotX Feb 01 '20 at 08:53

5 Answers5

16

Environmental configs are best kept in environment variables, in my experience. You can rely on global variables like so:

var envSetting = os.Getenv("TEST_ENV")

Alternatively, if using flags is a requirement, you could place your initialization code inside a function called init().

func init() {
    flags.Parse()
    myEnv = *envFlag
    // ...
}
summea
  • 7,390
  • 4
  • 32
  • 48
  • thanks, and you mean call init in every test that depends on it, right, this init is not a magical test setup thing, correct? – Ali Jan 25 '14 at 15:44
  • I recommend using only env vars in tests to avoid collision with the test runner's command line arguments. – Not_a_Golfer Jan 25 '14 at 16:54
  • I'm not sure if the init() trick would work. Any function called 'init' runs automatically before main(), but doesn't run at all when calling 'go test' http://golang.org/ref/spec#Program_execution "An init function cannot be referred to from anywhere in a program. In particular, init cannot be called explicitly, nor can a pointer to init be assigned to a function variable." – Tyler Jan 25 '14 at 18:04
  • 3
    @MatrixFrog - Are you just guessing or making a statement? That information is false. I use `init` in my own tests just fine, to set up some global test resources for the entire process. – jdi Jan 25 '14 at 18:47
  • @MatrixFrog That doesn't mean that init won't be called. There's still a main function that runs, it's just not yours. – Cubic Jan 25 '14 at 19:02
  • 5
    I wrote a test to verify that init() is not called when testing. But I did it wrong -- you're right, init() is still called. Sorry, nevermind! – Tyler Jan 25 '14 at 19:10
14

You can directly test main function and pass arguments.

Simple example showing a flag, and a pair of positional arguments

Note: Do NOT call it 'TestMain' that has a special meaning to the testing framework as of Go 1.8.

package main

import (
    "os"
    "testing"
)

func TestMainFunc(t *testing.T) {

    os.Args = append(os.Args, "--addr=http://b.com:566/something.avsc")
    os.Args = append(os.Args, "Get")
    os.Args = append(os.Args, `./some/resource/fred`)

    main()

    // Test results here, and decide pass/fail.
}
Mark Farnan
  • 141
  • 1
  • 2
  • 1
    You are almost there, as main will get your arguments. But there are 2 problems at least I see with this. The first it that its unclear what will happen when you call `main` as it different depending on the application (so will it loop or exit), so this may not work for the OP. The second is that you have replaced the `os.Args` so what will happen for other test that are run. – b01 Jun 06 '21 at 12:04
9

An alternative approach is to make main() be a stub that merely calls into another function after arguments are processed by flag.Parse(), for example:

var flagvar int
func init() {
    flag.IntVar(&flagvar, "flagname", 1234, "help for flagname")
}

func main() {
    flag.Parse()
    submain(flag.Args)
}

func submain(args []string) {
   ...
}

Then in your tests, flag variables can be set and arguments established before calling submain(...) simulating the command line establishment of flags and arguments. This approach can be used to maximize test coverage without actually using a command line. For example, in main_test.go, you might write:

func TestSomething(t *testing.T) {
    flagvar = 23
    args := []string{"a", "b", "c"}
    submain(args)
    ...
}
pajato0
  • 3,628
  • 3
  • 31
  • 37
  • If `submain()` validates `args` (for instance, a `--port` that must be between 1025 and 65535), how would you construct a test that `submain()` executed `os.Exit(1)` upon an improper `--port` flag? – pinate Aug 08 '16 at 20:35
  • @nate a solution might be to return an error from ```submain``` if validation fails and check for that in your test – matthewmcneely Nov 15 '16 at 03:26
6
os.Args[1] = "-conf=my.conf"
flag.Parse()

Notice that the config file name is hard-coded.

msanford
  • 11,803
  • 11
  • 66
  • 93
Jules
  • 69
  • 1
  • 2
  • 2
    If the cli wasn't passed any args this will throw an index out of range error. Explicitly overriding the os.Args []string might be the better option. `os.Args = []string{"noop", "-flag1=val1", "arg1", "arg2"}` – agentargo Aug 09 '15 at 19:58
0

While I was working through some of the issues with using init() to parse flags during my test setup, I found jbowen's post on this subject which provides a simpler way of accessing the incoming flags in a test run, based on the fact that flag.Parse() is already executed by go test.

var stringArg = flag.String("my-arg", "default", "value for my-arg")

This needs to be defined in the package scope so that the flag module picks it up before parsing. Otherwise it provides you easy access to an additionally defined flag and I imagine it could be defined in a reusable test fixture package.

JoshRivers
  • 9,920
  • 8
  • 39
  • 39