I have a method Sync() that overrides Config
field's values that are set in the environment. The environment variable names are derived from config fields by underscoring, and uppercasing the name. E.g. AppName will have a corresponding environment variable APP_NAME
Please help me to test the following cases. There are complex things like https://golang.org/pkg/reflect/#Value.Set:
Set assigns x to the value v. It panics if CanSet returns false. As in Go, x's value must be assignable to v's type.
So I don't know how to test this case?
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/BurntSushi/toml"
"github.com/fatih/camelcase"
"gopkg.in/yaml.v2"
)
type Config struct {
AppName string
BaseURL string
Port int
Verbose bool
StaticDir string
ViewsDir string
}
func (c *Config) Sync() error {
cfg := reflect.ValueOf(c).Elem()
cTyp := cfg.Type()
for k := range make([]struct{}, cTyp.NumField()) {
field := cTyp.Field(k)
cm := getEnvName(field.Name)
env := os.Getenv(cm)
if env == "" {
continue
}
switch field.Type.Kind() {
case reflect.String:
cfg.FieldByName(field.Name).SetString(env)
case reflect.Int:
v, err := strconv.Atoi(env)
if err != nil {
return fmt.Errorf("loading config field %s %v", field.Name, err)
}
cfg.FieldByName(field.Name).Set(reflect.ValueOf(v))
case reflect.Bool:
b, err := strconv.ParseBool(env)
if err != nil {
return fmt.Errorf("loading config field %s %v", field.Name, err)
}
cfg.FieldByName(field.Name).SetBool(b)
}
}
return nil
}
If you want to test changes made to Cofig
after calling Sync
, in your tests define a function that sets the environment:
func SetTestEnv(key, value string) error; err != nil {
if err := os.Setenv(key, value string) {
return err
}
return nil
}
Now in you test function for Sync
, create a test config, initialize test environment using the above method and call Sync
on the config value. strconv
defines NumError
specifically for failed conversions. You can make use of that:
func TestSync(t *testing.T) {
c : Config{
// initialize config, or do it through another method
// e.g. func InitConfig(...) *Config {..}
}
// set the environment
SetTestEnv("APP_NAME", "app name")
SetTestEnv("BASE_URL", "base url")
SetTestEnv("PORT", "port number") // will cause error
// etc..
if err := c.Sync(); err != nil {
e, ok := err.(*strconv.NumError)
if !ok {
t.Errorf("Unexpected error")
} else if e.Err != strconv.ErrSyntax { // check specifically for expected syntax error
t.Errorf("Expected conversion to fail")
}
}
SetTestEnv("PORT", "1000") // correct port number
if err := c.Sync(); err != nil {
t.Errorf("Unexpected error in Sync: %v", err)
}
}
Since you are ensuring Set
is called with correct value type using type switches, there should not be any cause for panic to occur.