Repository: evcc-io/evcc
Pull request #6760 (2023-03-10 18:40:37)

chore: refactor testing templates to reduce duplicated code
Fix https://github.com/evcc-io/evcc/issues/6757


Patch:
diff --git a/util/templates/render_instance.go b/util/templates/render_instance.go
new file mode 100644
index 00000000..858834e1
--- /dev/null
+++ b/util/templates/render_instance.go
@@ -0,0 +1,43 @@
+package templates
+
+import (
+	"errors"
+
+	"github.com/evcc-io/evcc/util"
+	"gopkg.in/yaml.v3"
+)
+
+// Instance is an actual instantiated template
+type Instance struct {
+	Type  string
+	Other map[string]interface{} `yaml:",inline"`
+}
+
+// RenderInstance renders an actual configuration instance
+func RenderInstance(class Class, other map[string]interface{}) (Instance, error) {
+	var cc struct {
+		Template string
+		Other    map[string]interface{} `mapstructure:",remain"`
+	}
+
+	if err := util.DecodeOther(other, &cc); err != nil {
+		return *new(Instance), err
+	}
+
+	tmpl, err := ByName(class, cc.Template)
+	if err != nil {
+		return *new(Instance), err
+	}
+
+	b, _, err := tmpl.RenderResult(TemplateRenderModeInstance, other)
+	if err != nil {
+		return *new(Instance), err
+	}
+
+	var instance Instance
+	if err = yaml.Unmarshal(b, &instance); err == nil && instance.Type == "" {
+		err = errors.New("empty instance type- check for missing usage")
+	}
+
+	return instance, err
+}
diff --git a/util/templates/render.go b/util/templates/render_testing.go
similarity index 52%
rename from util/templates/render.go
rename to util/templates/render_testing.go
index 554373cc..55e73663 100644
--- a/util/templates/render.go
+++ b/util/templates/render_testing.go
@@ -1,52 +1,16 @@
 package templates
 
 import (
-	"errors"
 	"os"
 	"testing"
 
-	"github.com/evcc-io/evcc/util"
 	"github.com/jinzhu/copier"
+	"golang.org/x/exp/slices"
 	"gopkg.in/yaml.v3"
 )
 
-// Instance is an actual instantiated template
-type Instance struct {
-	Type  string
-	Other map[string]interface{} `yaml:",inline"`
-}
-
-// RenderInstance renders an actual configuration instance
-func RenderInstance(class Class, other map[string]interface{}) (Instance, error) {
-	var cc struct {
-		Template string
-		Other    map[string]interface{} `mapstructure:",remain"`
-	}
-
-	if err := util.DecodeOther(other, &cc); err != nil {
-		return *new(Instance), err
-	}
-
-	tmpl, err := ByName(class, cc.Template)
-	if err != nil {
-		return *new(Instance), err
-	}
-
-	b, _, err := tmpl.RenderResult(TemplateRenderModeInstance, other)
-	if err != nil {
-		return *new(Instance), err
-	}
-
-	var instance Instance
-	if err = yaml.Unmarshal(b, &instance); err == nil && instance.Type == "" {
-		err = errors.New("empty instance type- check for missing usage")
-	}
-
-	return instance, err
-}
-
-// RenderTest renders and instantiates plus yaml-parses the template per usage
-func RenderTest(t *testing.T, tmpl Template, values map[string]interface{}, cb func(values map[string]interface{})) {
+// renderTest renders and instantiates plus yaml-parses the template per usage
+func renderTest(t *testing.T, tmpl Template, values map[string]interface{}, cb func(values map[string]interface{})) {
 	t.Run(tmpl.Template, func(t *testing.T) {
 		t.Parallel()
 
@@ -86,3 +50,33 @@ func RenderTest(t *testing.T, tmpl Template, values map[string]interface{}, cb f
 		}
 	})
 }
+
+func TestClass(t *testing.T, class Class, instantiate func(t *testing.T, values map[string]interface{})) {
+	for _, tmpl := range ByClass(class) {
+		tmpl := tmpl
+
+		t.Run(tmpl.Template, func(t *testing.T) {
+			// set default values for all params
+			values := tmpl.Defaults(TemplateRenderModeUnitTest)
+
+			// set the template value which is needed for rendering
+			values["template"] = tmpl.Template
+
+			// set modbus default test values
+			if values[ParamModbus] != nil {
+				modbusChoices := tmpl.ModbusChoices()
+				// we only test one modbus setup
+				if slices.Contains(modbusChoices, ModbusChoiceTCPIP) {
+					values[ModbusKeyTCPIP] = true
+				} else {
+					values[ModbusKeyRS485TCPIP] = true
+				}
+				tmpl.ModbusValues(TemplateRenderModeInstance, values)
+			}
+
+			renderTest(t, tmpl, values, func(values map[string]interface{}) {
+				instantiate(t, values)
+			})
+		})
+	}
+}


Test patch:
diff --git a/charger/template_test.go b/charger/template_test.go
index 2a2bc8a0..33bd207c 100644
--- a/charger/template_test.go
+++ b/charger/template_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/evcc-io/evcc/util/templates"
 	"github.com/evcc-io/evcc/util/test"
-	"golang.org/x/exp/slices"
 )
 
 var acceptable = []string{
@@ -31,31 +30,10 @@ var acceptable = []string{
 }
 
 func TestTemplates(t *testing.T) {
-	for _, tmpl := range templates.ByClass(templates.Charger) {
-		tmpl := tmpl
-
-		// set default values for all params
-		values := tmpl.Defaults(templates.TemplateRenderModeUnitTest)
-
-		// set the template value which is needed for rendering
-		values["template"] = tmpl.Template
-
-		// set modbus default test values
-		if values[templates.ParamModbus] != nil {
-			modbusChoices := tmpl.ModbusChoices()
-			// we only test one modbus setup
-			if slices.Contains(modbusChoices, templates.ModbusChoiceTCPIP) {
-				values[templates.ModbusKeyTCPIP] = true
-			} else {
-				values[templates.ModbusKeyRS485TCPIP] = true
-			}
-			tmpl.ModbusValues(templates.TemplateRenderModeInstance, values)
+	templates.TestClass(t, templates.Charger, func(t *testing.T, values map[string]any) {
+		if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
+			t.Log(values)
+			t.Error(err)
 		}
-
-		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
-			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
-				t.Error(err)
-			}
-		})
-	}
+	})
 }
diff --git a/meter/template_test.go b/meter/template_test.go
index de18cf16..0bd7002b 100644
--- a/meter/template_test.go
+++ b/meter/template_test.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/evcc-io/evcc/util/templates"
 	"github.com/evcc-io/evcc/util/test"
-	"golang.org/x/exp/slices"
 )
 
 var acceptable = []string{
@@ -14,8 +13,8 @@ var acceptable = []string{
 	"missing token",
 	"mqtt not configured",
 	"not a SunSpec device",
-	"missing password",         // Powerwall
-	"missing user or password", // Tapo
+	"missing credentials", // sockets
+	"missing password",    // Powerwall
 	"connect: no route to host",
 	"connect: connection refused",
 	"connect: network is unreachable",
@@ -31,32 +30,10 @@ var acceptable = []string{
 }
 
 func TestTemplates(t *testing.T) {
-	for _, tmpl := range templates.ByClass(templates.Meter) {
-		tmpl := tmpl
-
-		// set default values for all params
-		values := tmpl.Defaults(templates.TemplateRenderModeUnitTest)
-
-		// set the template value which is needed for rendering
-		values["template"] = tmpl.Template
-
-		// set modbus default test values
-		if values[templates.ParamModbus] != nil {
-			modbusChoices := tmpl.ModbusChoices()
-			// we only test one modbus setup
-			if slices.Contains(modbusChoices, templates.ModbusChoiceTCPIP) {
-				values[templates.ModbusKeyTCPIP] = true
-			} else {
-				values[templates.ModbusKeyRS485TCPIP] = true
-			}
-			tmpl.ModbusValues(templates.TemplateRenderModeInstance, values)
+	templates.TestClass(t, templates.Meter, func(t *testing.T, values map[string]any) {
+		if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
+			t.Log(values)
+			t.Error(err)
 		}
-
-		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
-			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
-				t.Log(values)
-				t.Error(err)
-			}
-		})
-	}
+	})
 }
diff --git a/vehicle/template_test.go b/vehicle/template_test.go
index d8ef2f1e..770c6244 100644
--- a/vehicle/template_test.go
+++ b/vehicle/template_test.go
@@ -36,19 +36,10 @@ var acceptable = []string{
 }
 
 func TestTemplates(t *testing.T) {
-	for _, tmpl := range templates.ByClass(templates.Vehicle) {
-		tmpl := tmpl
-
-		// set default values for all params
-		values := tmpl.Defaults(templates.TemplateRenderModeUnitTest)
-
-		// set the template value which is needed for rendering
-		values["template"] = tmpl.Template
-
-		templates.RenderTest(t, tmpl, values, func(values map[string]interface{}) {
-			if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
-				t.Error(err)
-			}
-		})
-	}
+	templates.TestClass(t, templates.Vehicle, func(t *testing.T, values map[string]any) {
+		if _, err := NewFromConfig("template", values); err != nil && !test.Acceptable(err, acceptable) {
+			t.Log(values)
+			t.Error(err)
+		}
+	})
 }