Search code examples
gorelative-path

Problem with resolving relative paths in non-main package


I'm having trouble resolving relative file paths in my Go application. For this app I decided to make a package that offers a uniform interface to the different configuration files. The conf package contains the relevant data files, so this is basically the file tree:

app/conf
    + config.go
    + config.json
    + ...
app/code
    + code.go
    + code_test.go

Problem is, when the tests defined in app/code/code_test.go calls a function in the app/conf package, which in turn tries to open app/conf/config.json, the relative path is messed up since the working directory is at app/code.

  • I've looked at other SO answers that mentions the path/filepath package and especially the filepath.Abs function for turning relative paths to absolute paths. However, this won't solve my problem since the absolute path will be based on the wrong working directory.

  • Some solution with "absolute paths" based on GOPATH would probably suffice, but I imagine the GOPATH would have little meaning when the code is built and exported.

  • Simply porting all configuration files to hard coded Go structs is not viable either as they're used cross-language.


Solution

  • Relying on a config file path within your source is not only an issue in unit tests, but it will also be an issue in production. Typically, code would do something like this:

    • The config handler accepts an io.Reader which it will read configuration from.
    • main will open a file (whose path might be hard-coded, passed by command line, passed by env var, etc) and pass it to the config handler for reading.
    • Unit tests of the config handler will instead hard-code a config (or multiple configs, to test different scenarios) into something like bytes.Buffer and pass that to the config handler to read instead.
    • Unit tests of anything other than the code which reads the config file (so, anything that uses the configuration but does not manipulate it), would generate the Config struct in code rather than reading it from a real or fake file, as part of the test harness. For example, myConf := config.Config{SomeProp: "foo", OtherProp: true} and then pass that to the function under test.