Repository: helm-unittest/helm-unittest

Issue:
Replace gopkg.in/yaml.v3 dependency as it is outdated and unmaintained
**Describe the bug**
There has been no activity on the `gopkg.in/yaml.v3` repo from its maintainers for over two years. There are over 100 open PRs and nearly 300 unresolved issues, including one from a contributor offering to take over maintainership. Most of the mentioned issues indirectly affect helm-unittest.

**To Reproduce**
Go to https://github.com/go-yaml/yaml and see there is no activity from the maintainer.

**Expected behavior**
helm-unittest uses up-to-date and maintained dependencies.



Hints from issue comments:
@ivankatliarchuk: There is no drop-in replacement. 

Example helm uses `sigs.k8s.io/yaml` which is a wrapper/fork `https://github.com/kubernetes-sigs/yaml/tree/master/goyaml.v3`@ivankatliarchuk: 5+ years old unmainted dependent library "https://github.com/dprotaso/go-yit" and 4+ unmainted library "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"


Problem with `gopkg.in/yaml.v3` it uses no interfaces, so the only way is to rewrite things from the ground@quintush: Hello @V02460 

Thanks for the feedback.
As mentioned by @ivankatliarchuk there are more libraries outdated.

We have started isolating the libraries, and will make contact to other maintainers to see if they are also willing to update their libraries.

Currently some of the used libraries have functionality where I haven't found a replacement for.

Greetings,
@quintush @afalhambra-hivemq: Is there any update about this?@ivankatliarchuk: Helm itself still rely on same dependency https://github.com/helm/helm/blob/4d580c6b95760be89a5131fa4ca7cd9e13cec792/go.mod#L37

I manage to get quite close with https://github.com/goccy/go-yaml, but at the moment not convinced is not going to break things@ivankatliarchuk: Helm recently migrated to https://github.com/yaml/go-yaml/tree/main, is here https://github.com/helm/helm/blob/c5901abba66a634e5854bdc89fa10b0a392ae937/go.mod#L34 and kube yaml is migration to same library https://github.com/kubernetes-sigs/yaml/issues/18

Solution patch:
diff --git a/.gitignore b/.gitignore
index 4c36290..11bd9fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,5 @@ _dist
 
 .idea
 
-.direnv
\ No newline at end of file
+.direnv
+cover.out
diff --git a/Makefile b/Makefile
index 309c4f7..a9446c9 100644
--- a/Makefile
+++ b/Makefile
@@ -28,8 +28,8 @@ help:
 
 .PHONY: plugin-dir
 plugin-dir:
-  HELM_3_PLUGINS := $(shell bash -c 'eval $$(helm env); echo $$HELM_PLUGINS')
-  HELM_PLUGIN_DIR := $(HELM_3_PLUGINS)/helm-unittest
+	HELM_3_PLUGINS := $(shell bash -c 'eval $$(helm env); echo $$HELM_PLUGINS')
+	HELM_PLUGIN_DIR := $(HELM_3_PLUGINS)/helm-unittest
 
 .PHONY: install
 install: bootstrap build plugin-dir
@@ -50,6 +50,11 @@ hookInstall: bootstrap build
 unittest: ## Run unit tests
 	go test ./... -v -cover
 
+.PHONY: test-coverage
+test-coverage: ## Test coverage with open report in default browser
+	@go test -cover -coverprofile=cover.out -v ./...
+	@go tool cover -html=cover.out
+
 .PHONY: build-debug
 build-debug: ## Compile packages and dependencies with debug flag
 	go build -o untt-dbg -gcflags "all=-N -l" ./cmd/helm-unittest
@@ -89,7 +94,7 @@ dependency: ## Dependency maintanance
 	go mod tidy
 
 .PHONY: dockerimage
-dockerimage: build
+dockerimage: build ## Build docker image
 	docker build --no-cache --build-arg HELM_VERSION=$(HELM_VERSION) -t $(DOCKER):$(VERSION) -f AlpineTest.Dockerfile .
 
 .PHONY: test-docker
diff --git a/internal/common/utilities.go b/internal/common/utilities.go
index 6f7a412..37e019d 100644
--- a/internal/common/utilities.go
+++ b/internal/common/utilities.go
@@ -3,14 +3,39 @@ package common
 import (
 	"bytes"
 	"strings"
+	"testing"
 
-	yaml "gopkg.in/yaml.v3"
+	"io"
+
+	"github.com/stretchr/testify/assert"
+	yamlv3 "gopkg.in/yaml.v3"
+	yaml "sigs.k8s.io/yaml"
 )
 
+type YamlNode struct {
+	Node yamlv3.Node
+}
+
+func NewYamlNode() YamlNode {
+	return YamlNode{
+		Node: yamlv3.Node{},
+	}
+}
+
+// YamlNewDecoder returns a new decoder that reads from r.
+func YamlNewDecoder(r io.Reader) *yamlv3.Decoder {
+	return yamlv3.NewDecoder(r)
+}
+
+// YamlNewEncoder returns a new encoder that writes to w.
+func YamlNewEncoder(w io.Writer) *yamlv3.Encoder {
+	return yamlv3.NewEncoder(w)
+}
+
 // TrustedMarshalYAML marshal yaml without error returned, if an error happens it panics
 func TrustedMarshalYAML(d interface{}) string {
 	byteBuffer := new(bytes.Buffer)
-	yamlEncoder := yaml.NewEncoder(byteBuffer)
+	yamlEncoder := yamlv3.NewEncoder(byteBuffer)
 	yamlEncoder.SetIndent(YAMLINDENTION)
 	defer yamlEncoder.Close()
 	if err := yamlEncoder.Encode(d); err != nil {
@@ -22,9 +47,39 @@ func TrustedMarshalYAML(d interface{}) string {
 // TrustedUnmarshalYAML unmarshal yaml without error returned, if an error happens it panics
 func TrustedUnmarshalYAML(d string) map[string]interface{} {
 	parsedYaml := K8sManifest{}
-	yamlDecoder := yaml.NewDecoder(strings.NewReader(d))
+	yamlDecoder := yamlv3.NewDecoder(strings.NewReader(d))
 	if err := yamlDecoder.Decode(&parsedYaml); err != nil {
 		panic(err)
 	}
 	return parsedYaml
 }
+
+func YamlToJson(in string) ([]byte, error) {
+	return yaml.YAMLToJSON([]byte(in))
+}
+
+func YmlUnmarshal(in string, out interface{}) error {
+	err := yamlv3.Unmarshal([]byte(in), out)
+	return err
+}
+
+func YmlUnmarshalTestHelper(input string, out any, t *testing.T) {
+	t.Helper()
+	err := YmlUnmarshal(input, out)
+	assert.NoError(t, err)
+}
+
+func YmlMarshall(in interface{}) (string, error) {
+	out, err := yaml.Marshal(in)
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
+
+func YmlMarshallTestHelper(in interface{}, t *testing.T) string {
+	t.Helper()
+	out, err := yaml.Marshal(in)
+	assert.NoError(t, err)
+	return string(out)
+}



Test patch:
diff --git a/pkg/unittest/.snapshots/TestV3RunnerOkWithAbsoluteOverrideValuesPassedTests b/pkg/unittest/.snapshots/TestV3RunnerOkWithAbsoluteOverrideValuesPassedTests
index 5d42ce2..1c8cd18 100644
--- a/pkg/unittest/.snapshots/TestV3RunnerOkWithAbsoluteOverrideValuesPassedTests
+++ b/pkg/unittest/.snapshots/TestV3RunnerOkWithAbsoluteOverrideValuesPassedTests
@@ -12,13 +12,14 @@
  PASS  test ingress	../../test/data/v3/basic/tests/ingress_test.yaml
  PASS  test notes	../../test/data/v3/basic/tests/notes_test.yaml
  PASS  test override names and fullNames in Kubernetes resources	../../test/data/v3/basic/tests/namesOverride_test.yaml
+ PASS  test pod disruption budget	../../test/data/v3/basic/tests/pdp_test.yaml
  PASS  test service	../../test/data/v3/basic/tests/service_test.yaml
  PASS  test service account	../../test/data/v3/basic/tests/serviceaccount_test.yaml
 
 
 Charts:      1 passed, 1 total
-Test Suites: 12 passed, 12 total
-Tests:       34 passed, 34 total
+Test Suites: 13 passed, 13 total
+Tests:       40 passed, 40 total
 Snapshot:    4 passed, 4 total
 Time:        XX.XXXms
 
diff --git a/pkg/unittest/.snapshots/TestV3RunnerOkWithOverrideValuesPassedTests b/pkg/unittest/.snapshots/TestV3RunnerOkWithOverrideValuesPassedTests
index 5d42ce2..1c8cd18 100644
--- a/pkg/unittest/.snapshots/TestV3RunnerOkWithOverrideValuesPassedTests
+++ b/pkg/unittest/.snapshots/TestV3RunnerOkWithOverrideValuesPassedTests
@@ -12,13 +12,14 @@
  PASS  test ingress	../../test/data/v3/basic/tests/ingress_test.yaml
  PASS  test notes	../../test/data/v3/basic/tests/notes_test.yaml
  PASS  test override names and fullNames in Kubernetes resources	../../test/data/v3/basic/tests/namesOverride_test.yaml
+ PASS  test pod disruption budget	../../test/data/v3/basic/tests/pdp_test.yaml
  PASS  test service	../../test/data/v3/basic/tests/service_test.yaml
  PASS  test service account	../../test/data/v3/basic/tests/serviceaccount_test.yaml
 
 
 Charts:      1 passed, 1 total
-Test Suites: 12 passed, 12 total
-Tests:       34 passed, 34 total
+Test Suites: 13 passed, 13 total
+Tests:       40 passed, 40 total
 Snapshot:    4 passed, 4 total
 Time:        XX.XXXms
 
diff --git a/pkg/unittest/.snapshots/TestV3RunnerOkWithPassedTests b/pkg/unittest/.snapshots/TestV3RunnerOkWithPassedTests
index 5d42ce2..1c8cd18 100644
--- a/pkg/unittest/.snapshots/TestV3RunnerOkWithPassedTests
+++ b/pkg/unittest/.snapshots/TestV3RunnerOkWithPassedTests
@@ -12,13 +12,14 @@
  PASS  test ingress	../../test/data/v3/basic/tests/ingress_test.yaml
  PASS  test notes	../../test/data/v3/basic/tests/notes_test.yaml
  PASS  test override names and fullNames in Kubernetes resources	../../test/data/v3/basic/tests/namesOverride_test.yaml
+ PASS  test pod disruption budget	../../test/data/v3/basic/tests/pdp_test.yaml
  PASS  test service	../../test/data/v3/basic/tests/service_test.yaml
  PASS  test service account	../../test/data/v3/basic/tests/serviceaccount_test.yaml
 
 
 Charts:      1 passed, 1 total
-Test Suites: 12 passed, 12 total
-Tests:       34 passed, 34 total
+Test Suites: 13 passed, 13 total
+Tests:       40 passed, 40 total
 Snapshot:    4 passed, 4 total
 Time:        XX.XXXms
 
diff --git a/pkg/unittest/assertion_test.go b/pkg/unittest/assertion_test.go
index ad5b51e..148607f 100644
--- a/pkg/unittest/assertion_test.go
+++ b/pkg/unittest/assertion_test.go
@@ -8,7 +8,6 @@ import (
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/results"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/snapshot"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/yaml.v3"
 )
 
 func validateSucceededTestAssertions(
@@ -18,10 +17,9 @@ func validateSucceededTestAssertions(
 	renderedMap map[string][]common.K8sManifest) {
 
 	assertions := make([]Assertion, assertionCount)
-	err := yaml.Unmarshal([]byte(assertionsYAML), &assertions)
+	common.YmlUnmarshalTestHelper(assertionsYAML, &assertions, t)
 
 	a := assert.New(t)
-	a.Nil(err)
 
 	for idx, assertion := range assertions {
 		result := assertion.Assert(renderedMap, fakeSnapshotComparer(true), true, nil, &results.AssertionResult{Index: idx}, false)
@@ -36,7 +34,7 @@ func validateSucceededTestAssertions(
 	}
 }
 
-func TestAssertionUnmarshaledFromYAML(t *testing.T) {
+func TestAssertionUnmarshalFromYAML(t *testing.T) {
 	assertionsYAML := `
 - equal:
 - notEqual:
@@ -75,11 +73,10 @@ func TestAssertionUnmarshaledFromYAML(t *testing.T) {
 
 	a := assert.New(t)
 	assertionsAsMap := make([]map[string]interface{}, 33)
-	mapErr := yaml.Unmarshal([]byte(assertionsYAML), &assertionsAsMap)
-	a.Nil(mapErr)
+	common.YmlUnmarshalTestHelper(assertionsYAML, &assertionsAsMap, t)
+
 	assertions := make([]Assertion, 33)
-	assErr := yaml.Unmarshal([]byte(assertionsYAML), &assertions)
-	a.Nil(assErr)
+	common.YmlUnmarshalTestHelper(assertionsYAML, &assertions, t)
 
 	for idx, assertion := range assertions {
 		_, ok := assertionsAsMap[idx][assertion.AssertType]
@@ -152,8 +149,7 @@ func TestAssertionUnmarshaledFromYAMLWithNotTrue(t *testing.T) {
 	a := assert.New(t)
 
 	assertions := make([]Assertion, 29)
-	err := yaml.Unmarshal([]byte(assertionsYAML), &assertions)
-	a.Nil(err)
+	common.YmlUnmarshalTestHelper(assertionsYAML, &assertions, t)
 
 	for _, assertion := range assertions {
 		a.True(assertion.Not)
@@ -208,8 +204,7 @@ func TestReverseAssertionTheSameAsOriginalOneWithNotTrue(t *testing.T) {
 	a := assert.New(t)
 
 	assertions := make([]Assertion, 28)
-	err := yaml.Unmarshal([]byte(assertionsYAML), &assertions)
-	a.Nil(err)
+	common.YmlUnmarshalTestHelper(assertionsYAML, &assertions, t)
 
 	for idx := 0; idx < len(assertions); idx += 2 {
 		a.Equal(assertions[idx].Not, !assertions[idx+1].Not)
@@ -335,10 +330,9 @@ template: not-existed.yaml
 equal:
 `
 	assertion := new(Assertion)
-	err := yaml.Unmarshal([]byte(assertionYAML), &assertion)
+	common.YmlUnmarshalTestHelper(assertionYAML, &assertion, t)
 
 	a := assert.New(t)
-	a.Nil(err)
 
 	result := assertion.Assert(renderedMap, fakeSnapshotComparer(true), true, nil, &results.AssertionResult{Index: 0}, false)
 	a.Equal(&results.AssertionResult{
@@ -358,10 +352,9 @@ func TestAssertionAssertWhenTemplateNotSpecifiedAndNoDefault(t *testing.T) {
 	}
 	assertionYAML := "equal:"
 	assertion := new(Assertion)
-	err := yaml.Unmarshal([]byte(assertionYAML), &assertion)
+	common.YmlUnmarshalTestHelper(assertionYAML, &assertion, t)
 
 	a := assert.New(t)
-	a.Nil(err)
 	result := assertion.Assert(renderedMap, fakeSnapshotComparer(true), true, nil, &results.AssertionResult{Index: 0}, false)
 	a.Equal(&results.AssertionResult{
 		Index:      0,
@@ -384,10 +377,9 @@ documentIndex: 1
 equal:
 `
 	assertion := new(Assertion)
-	err := yaml.Unmarshal([]byte(assertionYAML), &assertion)
+	common.YmlUnmarshalTestHelper(assertionYAML, &assertion, t)
 
 	a := assert.New(t)
-	a.Nil(err)
 
 	result := assertion.Assert(renderedMap, fakeSnapshotComparer(true), true, nil, &results.AssertionResult{Index: 0}, false)
 	a.Equal(&results.AssertionResult{
diff --git a/pkg/unittest/files_test.go b/pkg/unittest/files_test.go
index 2736302..ba50ae0 100644
--- a/pkg/unittest/files_test.go
+++ b/pkg/unittest/files_test.go
@@ -1,10 +1,13 @@
 package unittest_test
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"testing"
+	"testing/fstest"
 
 	. "github.com/helm-unittest/helm-unittest/pkg/unittest"
 	"github.com/stretchr/testify/assert"
@@ -28,6 +31,11 @@ func assertArrayPathOsAgnostic(t *testing.T, expected, actual []string) {
 	assert.Equal(t, want, actual)
 }
 
+func assertArrayContainsOsAgnostic(t *testing.T, actual []string, expected string) {
+	t.Helper()
+	assert.Contains(t, actual, filepath.FromSlash(expected))
+}
+
 func TestGetFiles_ChartWithoutSubCharts(t *testing.T) {
 	filesHelper(t)
 	err := os.Chdir("../../test/data/v3/basic")
@@ -35,7 +43,7 @@ func TestGetFiles_ChartWithoutSubCharts(t *testing.T) {
 
 	actual, err := GetFiles(".", []string{"tests/*_test.yaml"}, false)
 	assert.NoError(t, err)
-	assert.Equal(t, len(actual), 11)
+	assert.Equal(t, len(actual), 12)
 }
 
 func TestGetFiles_ChartWithoutSubChartsNoDuplicates(t *testing.T) {
@@ -158,3 +166,155 @@ func TestGetFiles_ChartWithSubChartPatternMatchingChildTests(t *testing.T) {
 
 	assert.Equal(t, expected, actual)
 }
+
+func TestWithDifferentPatterns(t *testing.T) {
+	tmp := t.TempDir()
+	path := fmt.Sprintf("%s/%s", tmp, "./a/b/c/e")
+	err := os.MkdirAll(path, 0755)
+	assert.NoError(t, err)
+
+	fs := fstest.MapFS{
+		"a/b/c/first.yaml":     {Data: []byte("hi")},
+		"a/b/c/e/second.yaml":  {Data: []byte("hi")},
+		"a/b/third.yaml":       {Data: []byte("hi")},
+		"a/b/c/third.hcl":      {Data: []byte("hi")},
+		"a/b/c/file0.txt":      {Data: []byte("hi")},
+		"a/file1.txt":          {Data: []byte("hi")},
+		"a/b/c/file2.json":     {Data: []byte("hi")},
+		"a/b/file2.json":       {Data: []byte("hi")},
+		"file3.xml":            {Data: []byte("hi")},
+		"a/b/file4_test.csv":   {Data: []byte("hi")},
+		"a/b/c/e/file5.ini":    {Data: []byte("hi")},
+		"a/b/c/e/file6.log":    {Data: []byte("hi")},
+		"a/b/c/e/file7.conf":   {Data: []byte("hi")},
+		"a/b/file6.md":         {Data: []byte("hi")},
+		"a/b/c/e/file8.md":     {Data: []byte("hi")},
+		"a/b/c/e/file9.html":   {Data: []byte("hi")},
+		"a/b/c/e/file10.css":   {Data: []byte("hi")},
+		"a/b/c/file11.js":      {Data: []byte("hi")},
+		"a/b/file12_test.go":   {Data: []byte("hi")},
+		"a/b/c/e/file13.py":    {Data: []byte("hi")},
+		"a/file14_test.rb":     {Data: []byte("hi")},
+		"a/b/c/file15.php":     {Data: []byte("hi")},
+		"a/b/file16.sh":        {Data: []byte("hi")},
+		"a/b/c/e/file17.pl":    {Data: []byte("hi")},
+		"a/b/c/e/file18.rs":    {Data: []byte("hi")},
+		"a/b/c/e/file19.kt":    {Data: []byte("hi")},
+		"a/b/c/e/file20.swift": {Data: []byte("hi")},
+	}
+
+	for path, el := range fs {
+		err := os.WriteFile(filepath.Join(tmp, path), el.Data, 0644)
+		assert.NoError(t, err)
+	}
+
+	tests := []struct {
+		pattern     []string
+		expected    []string
+		skipWindows bool
+	}{
+		{
+			pattern: []string{"**/*.yaml"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/c/first.yaml", tmp),
+				fmt.Sprintf("%s/a/b/c/e/second.yaml", tmp),
+				fmt.Sprintf("%s/a/b/third.yaml", tmp),
+			},
+		},
+		{
+			pattern:  []string{"[a-z\\.\\/]*"},
+			expected: []string{},
+		},
+		{
+			pattern: []string{"/[a-z\\.\\/]*"},
+			expected: []string{
+				"/[a-z\\.\\/]*",
+			},
+			skipWindows: true,
+		},
+		{
+			pattern: []string{"**/*.log"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/c/e/file6.log", tmp),
+			},
+		},
+		{
+			pattern:  []string{"**/*.(json|js|log)"},
+			expected: []string{},
+		},
+		{
+			pattern: []string{"a/*/file*.json", "a/**/file*.txt"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/file2.json", tmp),
+				fmt.Sprintf("%s/a/file1.txt", tmp),
+				fmt.Sprintf("%s/a/b/c/file0.txt", tmp),
+			},
+		},
+		{
+			pattern: []string{"**/*.xml", "**/*.csv"},
+			expected: []string{
+				fmt.Sprintf("%s/file3.xml", tmp),
+				fmt.Sprintf("%s/a/b/file4_test.csv", tmp),
+			},
+		},
+		{
+			pattern:  []string{"a/b/c/e/**.md"},
+			expected: []string{},
+		},
+		{
+			pattern: []string{"a/b/c/e/*.md"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/c/e/file8.md", tmp),
+			},
+		},
+		{
+			pattern: []string{"a/*_test.rb", "a/b/**.rb", "a/b/c/**.php"},
+			expected: []string{
+				fmt.Sprintf("%s/a/file14_test.rb", tmp),
+			},
+		},
+		{
+			pattern: []string{"a/b/*_test.go", ".*11.js"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/file12_test.go", tmp),
+			},
+		},
+		{
+			pattern: []string{fmt.Sprintf("%s/a/b/*.sh", tmp), "**/*.pl"},
+			expected: []string{
+				fmt.Sprintf("%s/a/b/*.sh", tmp),
+				fmt.Sprintf("%s/a/b/c/e/file17.pl", tmp),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(fmt.Sprintf("with %s identified %d files", strings.Join(tt.pattern, ":"), len(tt.expected)), func(t *testing.T) {
+			if tt.skipWindows && runtime.GOOS == "windows" {
+				t.Skip("Skip this test on Windows")
+			}
+			var escapePattern []string
+			for _, p := range tt.pattern {
+				escapePattern = append(escapePattern, filepath.FromSlash(p))
+			}
+			files, _ := GetFiles(tmp, escapePattern, false)
+			assert.Equal(t, len(tt.expected), len(files))
+			for _, expected := range tt.expected {
+				assert.Contains(t, files, filepath.FromSlash(expected))
+			}
+		})
+	}
+}
+
+func TestGetFiles_GlobError(t *testing.T) {
+	tmp := t.TempDir()
+
+	path := fmt.Sprintf("%s/%s", tmp, "./a/b/c.d/e.f")
+	err := os.MkdirAll(path, 0755)
+	assert.NoError(t, err)
+
+	files, err := GetFiles(path, []string{"[**"}, false)
+	assert.Nil(t, files)
+	assert.Error(t, err)
+	assert.EqualError(t, err, "syntax error in pattern")
+}
diff --git a/pkg/unittest/snapshot/cache.go b/pkg/unittest/snapshot/cache.go
index 4a90a81..a09d475 100644
--- a/pkg/unittest/snapshot/cache.go
+++ b/pkg/unittest/snapshot/cache.go
@@ -125,7 +125,7 @@ func (s *Cache) StoreToFileIfNeeded() (bool, error) {
 
 	if s.IsUpdating || s.insertedCount > 0 || s.VanishedCount() > 0 {
 		byteBuffer := new(bytes.Buffer)
-		yamlEncoder := yaml.NewEncoder(byteBuffer)
+		yamlEncoder := common.YamlNewEncoder(byteBuffer)
 		yamlEncoder.SetIndent(common.YAMLINDENTION)
 		if err := yamlEncoder.Encode(s.current); err != nil {
 			return false, err
diff --git a/pkg/unittest/suits_schema_validation_test.go b/pkg/unittest/suits_schema_validation_test.go
index 05175a1..3b905d6 100644
--- a/pkg/unittest/suits_schema_validation_test.go
+++ b/pkg/unittest/suits_schema_validation_test.go
@@ -6,10 +6,10 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/helm-unittest/helm-unittest/internal/common"
 	"github.com/stretchr/testify/assert"
 
 	"github.com/xeipuuv/gojsonschema"
-	"sigs.k8s.io/yaml"
 )
 
 const testSuiteSchemaLocal string = "../../schema/helm-testsuite.json"
@@ -133,7 +133,7 @@ func TestValidateExampleChartsWithTestSuitsAgainstLocalSchema(t *testing.T) {
 			assert.NoError(t, err)
 			for _, el := range content {
 				for _, content := range el.FileContents {
-					json, err := yaml.YAMLToJSON([]byte(content))
+					json, err := common.YamlToJson(content)
 
 					assert.NoError(t, err)
 					assert.NotEmpty(t, json)
@@ -148,7 +148,7 @@ func TestValidateExampleChartsWithTestSuitsAgainstLocalSchema(t *testing.T) {
 					assert.True(t, result.Valid(), fmt.Sprintf("Schema '%s' and the document '%s' is not valid!!!", fullPath, el.FilePath))
 
 					if !result.Valid() {
-						fmt.Printf("See errors:\n")
+						fmt.Printf("See errors for file%s:\n", el.FilePath)
 						for _, desc := range result.Errors() {
 							fmt.Printf("- %s\n", desc)
 						}
diff --git a/pkg/unittest/test_job.go b/pkg/unittest/test_job.go
index f17696b..6370964 100644
--- a/pkg/unittest/test_job.go
+++ b/pkg/unittest/test_job.go
@@ -17,8 +17,6 @@ import (
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/valueutils"
 	log "github.com/sirupsen/logrus"
 
-	yaml "gopkg.in/yaml.v3"
-
 	v3chart "helm.sh/helm/v3/pkg/chart"
 	v3util "helm.sh/helm/v3/pkg/chartutil"
 	v3engine "helm.sh/helm/v3/pkg/engine"
@@ -83,7 +81,7 @@ func parseRenderError(regexPattern, errorMessage string) (string, map[string]str
 func parseYamlFile(rendered string) ([]common.K8sManifest, error) {
 	// Replace --- with ---\n to ensure yaml rendering is parsed correctly/
 	rendered = splitterPattern.ReplaceAllString(rendered, "\n---\n")
-	decoder := yaml.NewDecoder(strings.NewReader(rendered))
+	decoder := common.YamlNewDecoder(strings.NewReader(rendered))
 	parsedYamls := make([]common.K8sManifest, 0)
 
 	for {
@@ -208,7 +206,7 @@ func (t *TestJob) RunV3(
 		return result
 	}
 
-	outputOfFiles, renderSucceed, renderError := t.renderV3Chart(targetChart, userValues)
+	outputOfFiles, renderSucceed, renderError := t.renderV3Chart(targetChart, []byte(userValues))
 
 	writeError := writeRenderedOutput(renderPath, outputOfFiles)
 	if writeError != nil {
@@ -243,7 +241,7 @@ func (t *TestJob) RunV3(
 }
 
 // liberally borrows from helm-template
-func (t *TestJob) getUserValues() ([]byte, error) {
+func (t *TestJob) getUserValues() (string, error) {
 	base := map[string]interface{}{}
 	routes := spliteChartRoutes(t.chartRoute)
 
@@ -259,11 +257,11 @@ func (t *TestJob) getUserValues() ([]byte, error) {
 
 		bytes, err := os.ReadFile(valueFilePath)
 		if err != nil {
-			return []byte{}, err
+			return "", err
 		}
 
-		if err := yaml.Unmarshal(bytes, &value); err != nil {
-			return []byte{}, fmt.Errorf("failed to parse %s: %s", specifiedPath, err)
+		if err := common.YmlUnmarshal(string(bytes), &value); err != nil {
+			return "", fmt.Errorf("failed to parse %s: %s", specifiedPath, err)
 		}
 
 		base = v3util.MergeTables(scopeValuesWithRoutes(routes, value), base)
@@ -273,7 +271,7 @@ func (t *TestJob) getUserValues() ([]byte, error) {
 	for path, values := range t.globalSet {
 		setMap, err := valueutils.BuildValueOfSetPath(values, path)
 		if err != nil {
-			return []byte{}, err
+			return "", err
 		}
 
 		base = v3util.MergeTables(scopeValuesWithRoutes(routes, setMap), base)
@@ -282,13 +280,13 @@ func (t *TestJob) getUserValues() ([]byte, error) {
 	for path, values := range t.Set {
 		setMap, err := valueutils.BuildValueOfSetPath(values, path)
 		if err != nil {
-			return []byte{}, err
+			return "", err
 		}
 
 		base = v3util.MergeTables(scopeValuesWithRoutes(routes, setMap), base)
 	}
 	log.WithField(LOG_TEST_JOB, "get-user-values").Debug("values ", base)
-	return yaml.Marshal(base)
+	return common.YmlMarshall(base)
 }
 
 // render the chart and return result map
diff --git a/pkg/unittest/test_job_test.go b/pkg/unittest/test_job_test.go
index 2cb58ae..08603e5 100644
--- a/pkg/unittest/test_job_test.go
+++ b/pkg/unittest/test_job_test.go
@@ -3,16 +3,17 @@ package unittest_test
 import (
 	"fmt"
 	"os"
+	"path"
 
 	"testing"
 	"time"
 
 	"github.com/bradleyjkemp/cupaloy/v2"
+	"github.com/helm-unittest/helm-unittest/internal/common"
 	. "github.com/helm-unittest/helm-unittest/pkg/unittest"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/results"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/snapshot"
 	"github.com/stretchr/testify/assert"
-	"gopkg.in/yaml.v3"
 	v3chart "helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chart/loader"
 )
@@ -39,10 +40,9 @@ asserts:
       pattern: /z/
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	a := assert.New(t)
-	a.Nil(err)
 	a.Equal(tj.Name, "should do something")
 	a.Equal(tj.Values, []string{"values.yaml"})
 	a.Equal(tj.Set, map[string]interface{}{
@@ -50,14 +50,14 @@ asserts:
 		"x.y.z": "XYZ",
 	})
 	assertions := make([]*Assertion, 2)
-	assErr := yaml.Unmarshal([]byte(`
+	assErr := common.YmlUnmarshal(`
   - equal:
       path: a.b
       value: c
   - matchRegex:
       path: x.y
       pattern: /z/
-`), &assertions)
+`, &assertions)
 	a.Nil(assErr)
 	a.Equal(tj.Assertions, assertions)
 }
@@ -80,12 +80,11 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -112,13 +111,12 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, renderPath, &results.TestJobResult{})
 	defer os.RemoveAll(renderPath)
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -143,12 +141,11 @@ asserts:
       pattern: -basic$
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -173,12 +170,11 @@ asserts:
       pattern: -basic
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -206,12 +202,11 @@ asserts:
       path: metadata.name
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -232,12 +227,11 @@ asserts:
       errorMessage: The externalPort is required
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -262,13 +256,12 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, false, "", &results.TestJobResult{})
 	// Write Buffer
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -293,13 +286,12 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 	// Write Buffer
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -321,12 +313,11 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -349,33 +340,21 @@ asserts:
 `
 	a := assert.New(t)
 
-	file, fileErr := os.Create("testjob_test_TestRunJobWithValuesFile.yaml")
-	if fileErr != nil {
-		a.FailNow("Failed to create file")
-	}
-	_, writeErr := file.WriteString("nameOverride: mary-jane")
-	if writeErr != nil {
-		a.FailNow("Failed to write to file")
-	}
-	closeErr := file.Close()
-	if closeErr != nil {
-		a.FailNow("Failed to close file")
-	}
+	file := path.Join("_scratch", "testjob_test_TestRunJobWithValuesFile.yaml")
+	a.Nil(writeToFile("nameOverride: mary-jane", file))
+	defer os.RemoveAll(file)
 
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(fmt.Sprintf(manifest, file.Name())), &tj)
-	a.Nil(err)
+	common.YmlUnmarshalTestHelper(fmt.Sprintf(manifest, file), &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
-	a.FileExists(file.Name())
+	a.FileExists(file)
 	a.Nil(testResult.ExecError)
 	a.True(testResult.Passed)
 	a.Equal(1, len(testResult.AssertsResult))
-
-	os.Remove(file.Name())
 }
 
 func TestV3RunJobWithReleaseSettings(t *testing.T) {
@@ -393,12 +372,11 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -416,12 +394,11 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -441,12 +418,11 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.NotNil(testResult.ExecError)
@@ -468,15 +444,13 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
-
-	a := assert.New(t)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
+	a := assert.New(t)
 	a.Nil(testResult.ExecError)
 	a.True(testResult.Passed)
 	a.Equal(1, len(testResult.AssertsResult))
@@ -496,12 +470,11 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
-
-	a := assert.New(t)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, nil, true, "", &results.TestJobResult{})
+
+	a := assert.New(t)
 	a.False(testResult.Passed)
 	a.Equal(0, len(tj.Capabilities.APIVersions))
 }
@@ -519,13 +492,11 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
-
-	a := assert.New(t)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, nil, true, "", &results.TestJobResult{})
 
+	a := assert.New(t)
 	a.False(testResult.Passed)
 	a.Equal([]string{}, tj.Capabilities.APIVersions)
 	a.Equal("12", tj.Capabilities.MinorVersion)
@@ -543,14 +514,11 @@ asserts:
     template: templates/crd_backup.yaml
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
-
-	a := assert.New(t)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, nil, true, "", &results.TestJobResult{})
-	a.False(testResult.Passed)
-	a.Equal("7", tj.Capabilities.MinorVersion)
+	assert.False(t, testResult.Passed)
+	assert.Equal(t, "7", tj.Capabilities.MinorVersion)
 }
 
 func TestV3RunJobWithChartSettings(t *testing.T) {
@@ -573,14 +541,13 @@ asserts:
     template: templates/deployment.yaml
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
-	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
+	a := assert.New(t)
 	a.Nil(testResult.ExecError)
 	a.True(testResult.Passed)
 	a.Equal(2, len(testResult.AssertsResult))
@@ -597,12 +564,11 @@ asserts:
       errorMessage:	"error calling include: template: no template \"non-existing-named-template\" associated with template \"gotpl\""
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -620,12 +586,11 @@ asserts:
       errorMessage: "values don't meet the specifications of the schema(s) in the following chart(s):\nwith-schema:\n- (root): image is required\n"
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.NotNil(testResult.ExecError)
@@ -646,14 +611,13 @@ asserts:
   - failedTemplate: {}
 `
 	var tj TestJob
-	err := unmarshalJob(manifest, &tj)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
-	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
+	a := assert.New(t)
 	a.NotNil(testResult.ExecError)
 	a.True(testResult.Passed)
 	a.Equal(1, len(testResult.AssertsResult))
@@ -672,12 +636,11 @@ asserts:
   - notFailedTemplate: {}
 `
 	var tj TestJob
-	err := yaml.Unmarshal([]byte(manifest), &tj)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
-	a.Nil(err)
 	cupaloy.SnapshotT(t, makeTestJobResultSnapshotable(testResult))
 
 	a.Nil(testResult.ExecError)
@@ -701,8 +664,7 @@ asserts:
 `
 	var tj TestJob
 	a := assert.New(t)
-	err := unmarshalJob(manifest, &tj)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
@@ -724,15 +686,13 @@ asserts:
       pattern: "(.*-)?postgresql-0.8.3"
 `
 	var tj TestJob
-	a := assert.New(t)
-	err := unmarshalJob(manifest, &tj)
-	a.Nil(err)
+	unmarshalJobTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
-	a.Nil(testResult.ExecError)
-	a.True(testResult.Passed)
-	a.Equal(1, len(testResult.AssertsResult))
+	assert.Nil(t, testResult.ExecError)
+	assert.True(t, testResult.Passed)
+	assert.Equal(t, 1, len(testResult.AssertsResult))
 }
 
 func TestModifyChartMetadataVersions(t *testing.T) {
@@ -847,8 +807,7 @@ asserts:
 `
 	var tj TestJob
 	assert := assert.New(t)
-	err := yaml.Unmarshal([]byte(manifest), &tj)
-	assert.NoError(err)
+	common.YmlUnmarshalTestHelper(manifest, &tj, t)
 
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
@@ -869,8 +828,7 @@ asserts:
   - notSupportedAssert:
 `
 	var tj TestJob
-	yaml.Unmarshal([]byte(manifest), &tj)
-
+	common.YmlUnmarshal(manifest, &tj)
 	testResult := tj.RunV3(c, &snapshot.Cache{}, true, "", &results.TestJobResult{})
 
 	a := assert.New(t)
@@ -880,3 +838,94 @@ asserts:
 	a.Equal(testResult.AssertsResult[0].AssertType, "equal")
 	a.NotEqual(testResult.AssertsResult[0].AssertType, "notSupportedAssert")
 }
+
+func TestV3RunJobPatchArrayValue(t *testing.T) {
+	c, _ := loader.Load(testV3BasicChart)
+	manifest := `
+it: should patch second element
+template: templates/configmap.yaml
+values:
+- %s
+set:
+ ingress:
+  hosts[1]: should-override-second.local
+asserts:
+  - exists:
+      path: data["my.ingress.hosts"]
+  - notEqual:
+      path: data["my.ingress.hosts"]
+      value:
+      - null
+      - should-override-second.local
+  - equal:
+      path: data["my.ingress.hosts"]
+      value:
+      - chart-example-first.local
+      - chart-example-second.local
+      - chart-example-third.local
+`
+	a := assert.New(t)
+	file := path.Join("_scratch", "test_tmp_values.yaml")
+	a.Nil(writeToFile(`
+ingress:
+  hosts:
+    - chart-example-first.local
+    - chart-example-second.local
+    - chart-example-third.local
+`, file))
+	defer os.RemoveAll(file)
+
+	var tj TestJob
+	unmarshalJobTestHelper(fmt.Sprintf(manifest, file), &tj, t)
+
+	testResult := tj.RunV3(c, nil, true, "", &results.TestJobResult{})
+
+	assert.Nil(t, testResult.ExecError)
+	assert.True(t, testResult.Passed)
+	assert.Equal(t, 3, len(testResult.AssertsResult))
+}
+
+func TestV3RunJobPatchPointArrayValue(t *testing.T) {
+	c, _ := loader.Load(testV3BasicChart)
+	manifest := `
+it: should patch second element
+template: templates/configmap.yaml
+values:
+- %s
+set:
+ ingress.hosts[1]: should-override-second.local
+asserts:
+  - exists:
+      path: data["my.ingress.hosts"]
+  - equal:
+      path: data["my.ingress.hosts"]
+      value:
+      - null
+      - should-override-second.local
+  - notEqual:
+      path: data["my.ingress.hosts"]
+      value:
+      - chart-example-first.local
+      - should-override-second.local
+      - chart-example-third.local
+`
+	a := assert.New(t)
+	file := path.Join("_scratch", "test_tmp_values.yaml")
+	a.Nil(writeToFile(`
+ingress:
+  hosts:
+    - chart-example-first.local
+    - chart-example-second.local
+    - chart-example-third.local
+`, file))
+	defer os.RemoveAll(file)
+
+	var tj TestJob
+	unmarshalJobTestHelper(fmt.Sprintf(manifest, file), &tj, t)
+
+	testResult := tj.RunV3(c, nil, true, "", &results.TestJobResult{})
+
+	assert.Nil(t, testResult.ExecError)
+	assert.True(t, testResult.Passed)
+	assert.Equal(t, 3, len(testResult.AssertsResult))
+}
diff --git a/pkg/unittest/test_suite.go b/pkg/unittest/test_suite.go
index 01ffda1..4b67473 100644
--- a/pkg/unittest/test_suite.go
+++ b/pkg/unittest/test_suite.go
@@ -13,7 +13,6 @@ import (
 	"github.com/helm-unittest/helm-unittest/internal/common"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/results"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/snapshot"
-	"gopkg.in/yaml.v3"
 	v3loader "helm.sh/helm/v3/pkg/chart/loader"
 	v3util "helm.sh/helm/v3/pkg/chartutil"
 	v3engine "helm.sh/helm/v3/pkg/engine"
@@ -75,7 +74,7 @@ func createTestSuite(suiteFilePath string, chartRoute string, content string, st
 	}
 
 	// Use decoder to setup strict or unstrict
-	yamlDecoder := yaml.NewDecoder(strings.NewReader(content))
+	yamlDecoder := common.YamlNewDecoder(strings.NewReader(content))
 	yamlDecoder.KnownFields(strict)
 	if err := yamlDecoder.Decode(&suite); err != nil {
 		return &suite, err
diff --git a/pkg/unittest/test_suite_test.go b/pkg/unittest/test_suite_test.go
index 5965ee9..87cdd43 100644
--- a/pkg/unittest/test_suite_test.go
+++ b/pkg/unittest/test_suite_test.go
@@ -4,16 +4,15 @@ import (
 	"fmt"
 	"os"
 	"path"
-	"path/filepath"
 	"testing"
 	"time"
 
 	"github.com/bradleyjkemp/cupaloy/v2"
+	"github.com/helm-unittest/helm-unittest/internal/common"
 	. "github.com/helm-unittest/helm-unittest/pkg/unittest"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/results"
 	"github.com/helm-unittest/helm-unittest/pkg/unittest/snapshot"
 	"github.com/stretchr/testify/assert"
-	yaml "gopkg.in/yaml.v3"
 )
 
 // Most used test files
@@ -49,32 +48,6 @@ func makeTestSuiteResultSnapshotable(result *results.TestSuiteResult) *results.T
 	return result
 }
 
-// writeToFile writes the provided string data to a file with the given filename.
-// It returns an error if the file cannot be created or if there is an error during writing.
-func writeToFile(data string, filename string) error {
-	err := os.MkdirAll(filepath.Dir(filename), 0755)
-	if err != nil {
-		fmt.Println("Error creating folders for file:", err)
-		return err
-	}
-
-	// Create the file with an absolute path
-	file, err := os.Create(filename)
-	if err != nil {
-		fmt.Println("Error creating file:", err)
-		return err
-	}
-	defer file.Close()
-
-	_, err = file.WriteString(data)
-	if err != nil {
-		fmt.Println("Error writing to file:", err)
-		return err
-	}
-
-	return nil
-}
-
 func validateTestResultAndSnapshots(
 	t *testing.T,
 	suiteResult *results.TestSuiteResult,
@@ -98,7 +71,7 @@ func validateTestResultAndSnapshots(
 }
 
 // Helper metheod for the render process
-func getExpectedRenderedTestSuites(customSnapshotIds bool) map[string]*TestSuite {
+func getExpectedRenderedTestSuites(customSnapshotIds bool, t *testing.T) map[string]*TestSuite {
 	// multiple_suites_snapshot.yaml assertions
 	createSnapshotTestYaml := func(env string) string {
 		return fmt.Sprintf(`
@@ -109,9 +82,9 @@ asserts:
     - matchSnapshot: {}`, env)
 	}
 	snapshotDevTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(createSnapshotTestYaml("dev")), &snapshotDevTest)
+	common.YmlUnmarshalTestHelper(createSnapshotTestYaml("dev"), &snapshotDevTest, t)
 	snapshotProdTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(createSnapshotTestYaml("prod")), &snapshotProdTest)
+	common.YmlUnmarshalTestHelper(createSnapshotTestYaml("prod"), &snapshotProdTest, t)
 	// multiple_test_suites.yaml assertions
 	crateMultipleTestSuitesYaml := func(env string) string {
 		return fmt.Sprintf(`
@@ -132,9 +105,9 @@ asserts:
         decodeBase64: true`, env, env)
 	}
 	multipleTestSuitesDevTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(crateMultipleTestSuitesYaml("dev")), &multipleTestSuitesDevTest)
+	common.YmlUnmarshalTestHelper(crateMultipleTestSuitesYaml("dev"), &multipleTestSuitesDevTest, t)
 	multipleTestSuitesProdTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(crateMultipleTestSuitesYaml("prod")), &multipleTestSuitesProdTest)
+	common.YmlUnmarshalTestHelper(crateMultipleTestSuitesYaml("prod"), &multipleTestSuitesProdTest, t)
 	// multiple_tests_test.yaml assertions
 	var secretNameEqualsYaml = func(env string) string {
 		return fmt.Sprintf(`
@@ -150,16 +123,16 @@ asserts:
           - secretName: %s-my-tls-secret`, env, env, env)
 	}
 	multipleTestsDevTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(secretNameEqualsYaml("dev")), &multipleTestsDevTest)
+	common.YmlUnmarshalTestHelper(secretNameEqualsYaml("dev"), &multipleTestsDevTest, t)
 	multipleTestsProdTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(secretNameEqualsYaml("prod")), &multipleTestsProdTest)
+	common.YmlUnmarshalTestHelper(secretNameEqualsYaml("prod"), &multipleTestsProdTest, t)
 	const multipleTestsFirstTestYaml = `
 it: should render nothing if not enabled
 asserts:
     - hasDocuments:
         count: 0`
 	multipleTestsFirstTest := TestJob{}
-	_ = yaml.Unmarshal([]byte(multipleTestsFirstTestYaml), &multipleTestsFirstTest)
+	common.YmlUnmarshalTestHelper(multipleTestsFirstTestYaml, &multipleTestsFirstTest, t)
 
 	// Set up snapshotId values
 	// Note, this is completely based on the order of the yaml in a single suite template file
@@ -312,7 +285,7 @@ func TestV3RenderSuitesUnstrictFileOk(t *testing.T) {
 
 	a.Nil(err)
 
-	expectedSuites := getExpectedRenderedTestSuites(false)
+	expectedSuites := getExpectedRenderedTestSuites(false, t)
 
 	for _, suite := range suites {
 		a.Contains(expectedSuites, suite.Name, "Unexpected test suite"+suite.Name)
@@ -349,7 +322,7 @@ func TestV3RenderSuitesStrictFileOk(t *testing.T) {
 
 	a.Nil(err)
 
-	expectedSuites := getExpectedRenderedTestSuites(false)
+	expectedSuites := getExpectedRenderedTestSuites(false, t)
 
 	for _, suite := range suites {
 		a.Contains(expectedSuites, suite.Name, "Unexpected test suite"+suite.Name)
@@ -368,7 +341,7 @@ func TestV3RenderSuitesCustomSnapshotIdOk(t *testing.T) {
 
 	a.Nil(err)
 
-	expectedSuites := getExpectedRenderedTestSuites(true)
+	expectedSuites := getExpectedRenderedTestSuites(true, t)
 
 	for _, suite := range suites {
 		a.Contains(expectedSuites, suite.Name, "Unexpected test suite"+suite.Name)
@@ -387,8 +360,7 @@ tests:
     asserts:
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_noasserts_template_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
@@ -427,8 +399,7 @@ tests:
       - matchSnapshot: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_multiple_template_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
@@ -452,8 +423,7 @@ tests:
       - matchSnapshot: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_suite_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
@@ -489,8 +459,7 @@ tests:
       - matchSnapshot: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_suite_override_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
@@ -513,8 +482,7 @@ tests:
           value: Pod
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_failed_suite_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
@@ -537,8 +505,7 @@ tests:
       - matchSnapshot: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_subfolder_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3WithSubFolderChart, cache, true, "", &results.TestSuiteResult{})
@@ -560,8 +527,7 @@ tests:
       - matchSnapshot: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_subchart_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3WithSubChart, cache, true, "", &results.TestSuiteResult{})
@@ -585,8 +551,7 @@ tests:
         pattern: "(.*-)?postgresql-1.2.3"
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	suiteResult := testSuite.RunV3(testV3WithSubChart, &snapshot.Cache{}, true, "", &results.TestSuiteResult{})
 	assert.True(t, suiteResult.Passed)
@@ -606,8 +571,7 @@ tests:
       - notFailedTemplate: {}
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_subchartwithtrimming_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3WithSubChart, cache, true, "", &results.TestSuiteResult{})
@@ -637,8 +601,7 @@ tests:
           count: 0
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_subchartwithalias_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3WithSubChart, cache, true, "", &results.TestSuiteResult{})
@@ -664,8 +627,7 @@ tests:
           value: postgresql-0.8.3
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	suiteResult := testSuite.RunV3(testV3WithSubChart, &snapshot.Cache{}, true, "", &results.TestSuiteResult{})
 
@@ -693,8 +655,7 @@ tests:
           value: postgresql-0.6.3
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	suiteResult := testSuite.RunV3(testV3WithSubChart, &snapshot.Cache{}, true, "", &results.TestSuiteResult{})
 
@@ -724,8 +685,7 @@ tests:
           value: postgresql-0.7.1
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	suiteResult := testSuite.RunV3(testV3WithSubChart, &snapshot.Cache{}, true, "", &results.TestSuiteResult{})
 
@@ -748,8 +708,7 @@ tests:
           errorMessage: nameOverride cannot be longer than 20 characters
 `
 	testSuite := TestSuite{}
-	err := yaml.Unmarshal([]byte(suiteDoc), &testSuite)
-	assert.Nil(t, err)
+	common.YmlUnmarshalTestHelper(suiteDoc, &testSuite, t)
 
 	cache, _ := snapshot.CreateSnapshotOfSuite(path.Join(tmpdir, "v3_nameoverride_failed_suite_test.yaml"), false)
 	suiteResult := testSuite.RunV3(testV3BasicChart, cache, true, "", &results.TestSuiteResult{})
diff --git a/pkg/unittest/utils_test.go b/pkg/unittest/utils_test.go
index 3c22896..ef90d41 100644
--- a/pkg/unittest/utils_test.go
+++ b/pkg/unittest/utils_test.go
@@ -1,22 +1,51 @@
 package unittest_test
 
 import (
-	"gopkg.in/yaml.v3"
+	"fmt"
+	"os"
+	"path/filepath"
+	"testing"
 
+	"github.com/helm-unittest/helm-unittest/internal/common"
 	. "github.com/helm-unittest/helm-unittest/pkg/unittest"
+	"github.com/stretchr/testify/assert"
 )
 
-// UnmarshalJob unmarshall a YAML-encoded string into a TestJob struct.
+// unmarshalJobTestHelper unmarshall a YAML-encoded string into a TestJob struct.
 // It extracts the majorVersion, minorVersion, and apiVersions fields from
 // CapabilitiesFields and populates the corresponding fields in Capabilities.
 // If apiVersions is nil, it sets APIVersions to nil. If it's a slice,
 // it appends string values to APIVersions. Returns an error if unmarshaling
 // or processing fails.
-func unmarshalJob(input string, out *TestJob) error {
-	err := yaml.Unmarshal([]byte(input), &out)
+func unmarshalJobTestHelper(input string, out *TestJob, t *testing.T) {
+	t.Helper()
+	err := common.YmlUnmarshal(input, &out)
+	assert.NoError(t, err)
+	out.SetCapabilities()
+}
+
+// writeToFile writes the provided string data to a file with the given filename.
+// It returns an error if the file cannot be created or if there is an error during writing.
+func writeToFile(data string, filename string) error {
+	err := os.MkdirAll(filepath.Dir(filename), 0755)
 	if err != nil {
+		fmt.Println("Error creating folders for file:", err)
 		return err
 	}
-	out.SetCapabilities()
+
+	// Create the file with an absolute path
+	file, err := os.Create(filename)
+	if err != nil {
+		fmt.Println("Error creating file:", err)
+		return err
+	}
+	defer file.Close()
+
+	_, err = file.WriteString(data)
+	if err != nil {
+		fmt.Println("Error writing to file:", err)
+		return err
+	}
+
 	return nil
 }
diff --git a/pkg/unittest/validators/equal_or_greater_validator_test.go b/pkg/unittest/validators/equal_or_greater_validator_test.go
index 5fb8a46..5aec7fc 100644
--- a/pkg/unittest/validators/equal_or_greater_validator_test.go
+++ b/pkg/unittest/validators/equal_or_greater_validator_test.go
@@ -218,6 +218,29 @@ spec:
 	assert.Equal(t, []string{}, diff)
 }
 
+func TestEqualOrGreaterValidator_TODO(t *testing.T) {
+	var actual = `
+kind: PodDisruptionBudget
+metadata:
+  name: 'greaterorequal-test-pdb'
+spec:
+  minAvailable: 2
+  unhealthyPodEvictionPolicy: AlwaysAllow
+`
+	manifest := makeManifest(actual)
+
+	v := EqualOrGreaterValidator{
+		Path:  "spec.minAvailable",
+		Value: 1,
+	}
+	pass, _ := v.Validate(&ValidateContext{
+		Docs:     []common.K8sManifest{manifest},
+		Negative: false,
+	})
+
+	assert.False(t, pass)
+}
+
 func TestEqualOrGreaterValidatorWhenTypesDoNotMatch(t *testing.T) {
 	tests := []struct {
 		name, doc, path string
diff --git a/pkg/unittest/valueutils/valueutils.go b/pkg/unittest/valueutils/valueutils.go
index 47a9c9c..6df1789 100644
--- a/pkg/unittest/valueutils/valueutils.go
+++ b/pkg/unittest/valueutils/valueutils.go
@@ -4,11 +4,9 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"strconv"
 
 	"github.com/helm-unittest/helm-unittest/internal/common"
 	"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
-	yaml "gopkg.in/yaml.v3"
 )
 
 // GetValueOfSetPath get the value of the `--set` format path from a manifest
@@ -21,8 +19,8 @@ func GetValueOfSetPath(manifest common.K8sManifest, path string) ([]interface{},
 	byteBuffer := new(bytes.Buffer)
 
 	// Convert K8Manifest to yaml.Node
-	var rawManifest yaml.Node
-	yamlEncoder := yaml.NewEncoder(byteBuffer)
+	node := common.NewYamlNode()
+	yamlEncoder := common.YamlNewEncoder(byteBuffer)
 	yamlEncoder.SetIndent(common.YAMLINDENTION)
 
 	err := yamlEncoder.Encode(manifest)
@@ -30,9 +28,9 @@ func GetValueOfSetPath(manifest common.K8sManifest, path string) ([]interface{},
 		return nil, err
 	}
 
-	yamlDecoder := yaml.NewDecoder(byteBuffer)
+	yamlDecoder := common.YamlNewDecoder(byteBuffer)
 
-	if err := yamlDecoder.Decode(&rawManifest); err != nil {
+	if err := yamlDecoder.Decode(&node.Node); err != nil {
 		return nil, err
 	}
 
@@ -43,7 +41,7 @@ func GetValueOfSetPath(manifest common.K8sManifest, path string) ([]interface{},
 	}
 
 	// Search for nodes
-	manifestParts, err := yamlPath.Find(&rawManifest)
+	manifestParts, err := yamlPath.Find(&node.Node)
 	if err != nil {
 		return nil, err
 	}
@@ -73,8 +71,8 @@ func BuildValueOfSetPath(val interface{}, path string) (map[string]interface{},
 }
 
 type parseTraverser interface {
-	traverseMapKey(string) error
-	traverseListIdx(int) error
+	traverseMapKey(string)
+	traverseListIdx(int)
 }
 
 type buildTraverser struct {
@@ -82,14 +80,12 @@ type buildTraverser struct {
 	cursors []interface{}
 }
 
-func (tr *buildTraverser) traverseMapKey(key string) error {
+func (tr *buildTraverser) traverseMapKey(key string) {
 	tr.cursors = append(tr.cursors, key)
-	return nil
 }
 
-func (tr *buildTraverser) traverseListIdx(idx int) error {
+func (tr *buildTraverser) traverseListIdx(idx int) {
 	tr.cursors = append(tr.cursors, idx)
-	return nil
 }
 
 func (tr buildTraverser) getBuildedData() map[string]interface{} {
@@ -138,7 +134,8 @@ func traverseSetPath(in io.RuneReader, traverser parseTraverser, state int) erro
 		if err == io.EOF {
 			switch {
 			case len(k) != 0 && state == expectKey:
-				return traverser.traverseMapKey(string(k))
+				traverser.traverseMapKey(string(k))
+				return nil
 			case len(k) == 0 && state == expectDenotation:
 				return nil
 			default:
@@ -170,68 +167,6 @@ func traverseSetPath(in io.RuneReader, traverser parseTraverser, state int) erro
 	return nil
 }
 
-func handleExpectIndex(k []rune, last rune, traverser parseTraverser) (int, error) {
-	if last != ']' {
-		return -1, fmt.Errorf("missing index value")
-	}
-	idx, idxErr := strconv.Atoi(string(k))
-	if idxErr != nil {
-		return -1, idxErr
-	}
-	if e := traverser.traverseListIdx(idx); e != nil {
-		return -1, e
-	}
-	return expectDenotation, nil
-}
-
-func handleExpectDenotation(last rune) (int, error) {
-	switch last {
-	case '.':
-		return expectKey, nil
-	case '[':
-		return expectIndex, nil
-	default:
-		return -1, fmt.Errorf("invalid denotation token %s", string(last))
-	}
-}
-
-func handleExpectKey(k []rune, last rune, traverser parseTraverser) (int, error) {
-	switch last {
-	case '.':
-		if e := traverser.traverseMapKey(string(k)); e != nil {
-			return -1, e
-		}
-		return expectKey, nil
-	case '[':
-		if len(k) == 0 {
-			bufferedMapKey = ""
-			return expectEscaping, nil
-		}
-		if e := traverser.traverseMapKey(string(k)); e != nil {
-			return -1, e
-		}
-		return expectIndex, nil
-	default:
-		return -1, fmt.Errorf("invalid key %s", string(last))
-	}
-}
-
-func handleExpectEscaping(k []rune, last rune, traverser parseTraverser) (int, error) {
-	switch last {
-	case '.':
-		bufferedMapKey += string(k) + "."
-		return expectEscaping, nil
-	case ']':
-		bufferedMapKey += string(k)
-		if e := traverser.traverseMapKey(bufferedMapKey); e != nil {
-			return -1, e
-		}
-		return expectDenotation, nil
-	default:
-		return -1, fmt.Errorf("invalid escaping token %s", string(last))
-	}
-}
-
 // copy from helm
 func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
 	v := []rune{}
@@ -241,12 +176,6 @@ func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) {
 			return v, r, e
 		case inMap(r, stop):
 			return v, r, nil
-		case r == '\\':
-			next, _, e := in.ReadRune()
-			if e != nil {
-				return v, next, e
-			}
-			v = append(v, next)
 		default:
 			v = append(v, r)
 		}
diff --git a/pkg/unittest/valueutils/valueutils_handlers.go b/pkg/unittest/valueutils/valueutils_handlers.go
new file mode 100644
index 0000000..a18d419
--- /dev/null
+++ b/pkg/unittest/valueutils/valueutils_handlers.go
@@ -0,0 +1,60 @@
+package valueutils
+
+import (
+	"fmt"
+	"strconv"
+)
+
+func handleExpectIndex(k []rune, last rune, traverser parseTraverser) (int, error) {
+	if last != ']' {
+		return -1, fmt.Errorf("missing index value")
+	}
+	idx, idxErr := strconv.Atoi(string(k))
+	if idxErr != nil {
+		return -1, idxErr
+	}
+	traverser.traverseListIdx(idx)
+	return expectDenotation, nil
+}
+
+func handleExpectDenotation(last rune) (int, error) {
+	switch last {
+	case '.':
+		return expectKey, nil
+	case '[':
+		return expectIndex, nil
+	default:
+		return -1, fmt.Errorf("invalid denotation token %s", string(last))
+	}
+}
+
+func handleExpectKey(k []rune, last rune, traverser parseTraverser) (int, error) {
+	switch last {
+	case '.':
+		traverser.traverseMapKey(string(k))
+		return expectKey, nil
+	case '[':
+		if len(k) == 0 {
+			bufferedMapKey = ""
+			return expectEscaping, nil
+		}
+		traverser.traverseMapKey(string(k))
+		return expectIndex, nil
+	default:
+		return -1, fmt.Errorf("invalid key %s", string(last))
+	}
+}
+
+func handleExpectEscaping(k []rune, last rune, traverser parseTraverser) (int, error) {
+	switch last {
+	case '.':
+		bufferedMapKey += string(k) + "."
+		return expectEscaping, nil
+	case ']':
+		bufferedMapKey += string(k)
+		traverser.traverseMapKey(bufferedMapKey)
+		return expectDenotation, nil
+	default:
+		return -1, fmt.Errorf("invalid escaping token %s", string(last))
+	}
+}
diff --git a/pkg/unittest/valueutils/valueutils_handlers_test.go b/pkg/unittest/valueutils/valueutils_handlers_test.go
new file mode 100644
index 0000000..a6cb05b
--- /dev/null
+++ b/pkg/unittest/valueutils/valueutils_handlers_test.go
@@ -0,0 +1,238 @@
+package valueutils
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+type mockTraverser struct {
+	err error
+	buildTraverser
+	traverseMapKeyCount  int
+	traverseListIdxCount int
+}
+
+func newMockTraverser() *mockTraverser {
+	return &mockTraverser{
+		buildTraverser: buildTraverser{},
+	}
+}
+
+func (m *mockTraverser) traverseMapKey(key string) {
+	m.traverseMapKeyCount++
+	m.buildTraverser.traverseMapKey(key)
+	if key == "key-error" {
+		m.err = fmt.Errorf("traverser error")
+	}
+}
+
+func (m *mockTraverser) traverseListIdx(idx int) {
+	m.traverseListIdxCount++
+	m.buildTraverser.traverseListIdx(idx)
+	if idx == 6897 {
+		m.err = fmt.Errorf("traverser error")
+	}
+}
+
+func TestHandleExpectIndex_ErrorCases(t *testing.T) {
+	tr := newMockTraverser()
+	tests := []struct {
+		name                 string
+		runes                []rune
+		lastRune             rune
+		errMsg               string
+		traverseListIdxCount int
+	}{
+		{
+			name:                 "missing index value",
+			errMsg:               "missing index value",
+			runes:                []rune(""),
+			lastRune:             'a',
+			traverseListIdxCount: 0,
+		},
+		{
+			name:                 "invalid index value",
+			errMsg:               "invalid syntax",
+			runes:                []rune("abc"),
+			lastRune:             ']',
+			traverseListIdxCount: 0,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			state, err := handleExpectIndex(tt.runes, tt.lastRune, tr)
+			assert.Error(t, err)
+			assert.Equal(t, tt.traverseListIdxCount, tr.traverseListIdxCount)
+			assert.Equal(t, -1, state)
+		})
+	}
+}
+
+func TestHandleExpectIndex_OkCases(t *testing.T) {
+	tr := &mockTraverser{}
+	tests := []struct {
+		name     string
+		runes    []rune
+		lastRune rune
+	}{
+		{
+			name:     "valid index value",
+			runes:    []rune("123"),
+			lastRune: ']',
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			state, err := handleExpectIndex(tt.runes, tt.lastRune, tr)
+			assert.NoError(t, err)
+			assert.Equal(t, expectDenotation, state)
+		})
+	}
+}
+
+func TestHandleExpectDenotation_ValidDotToken(t *testing.T) {
+	tests := []struct {
+		name     string
+		lastRune rune
+		expected int
+	}{
+		{
+			name:     "valid token",
+			lastRune: '.',
+			expected: expectKey,
+		},
+		{
+			name:     "valid open bracket token",
+			lastRune: '[',
+			expected: expectIndex,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			state, err := handleExpectDenotation(tt.lastRune)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, state)
+		})
+	}
+}
+
+func TestHandleExpectDenotation_InvalidToken(t *testing.T) {
+	state, err := handleExpectDenotation('a')
+	assert.Error(t, err)
+	assert.EqualError(t, err, "invalid denotation token a")
+	assert.Equal(t, -1, state)
+}
+
+func TestHandleExpectKey_OkCases(t *testing.T) {
+	tests := []struct {
+		name     string
+		runes    []rune
+		lastRune rune
+		expected int
+	}{
+		{
+			name:     "valid dot token",
+			lastRune: '.',
+			runes:    []rune("key"),
+			expected: expectKey,
+		},
+		{
+			name:     "valid open bracket token with empty key",
+			lastRune: '[',
+			runes:    []rune(""),
+			expected: expectEscaping,
+		},
+		{
+			name:     "valid open bracket token with non-empty key",
+			lastRune: '[',
+			runes:    []rune("key"),
+			expected: expectIndex,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tr := newMockTraverser()
+			state, err := handleExpectKey(tt.runes, tt.lastRune, tr)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, state)
+		})
+	}
+}
+
+func TestHandleExpectKey_ErrorCases(t *testing.T) {
+	tests := []struct {
+		name        string
+		runes       []rune
+		lastRune    rune
+		errorString string
+	}{
+		{
+			name:        "invalid token",
+			lastRune:    'a',
+			runes:       []rune("key"),
+			errorString: "invalid key a",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tr := newMockTraverser()
+			state, err := handleExpectKey(tt.runes, tt.lastRune, tr)
+			assert.Error(t, err)
+			assert.Equal(t, tt.errorString, err.Error())
+			assert.Equal(t, -1, state)
+		})
+	}
+}
+
+// last
+func TestHandleExpectEscaping_OkCases(t *testing.T) {
+	tests := []struct {
+		name        string
+		runes       []rune
+		lastRune    rune
+		expected    int
+		bufferedKey string
+	}{
+		{
+			name:        "valid dot token",
+			lastRune:    '.',
+			runes:       []rune("key"),
+			expected:    expectEscaping,
+			bufferedKey: "key.",
+		},
+		{
+			name:        "valid close bracket token",
+			lastRune:    ']',
+			runes:       []rune("key"),
+			expected:    expectDenotation,
+			bufferedKey: "key",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			bufferedMapKey = ""
+			tr := newMockTraverser()
+			state, err := handleExpectEscaping(tt.runes, tt.lastRune, tr)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expected, state)
+			assert.Equal(t, tt.bufferedKey, bufferedMapKey)
+		})
+	}
+}
+
+func TestHandleExpectEscaping_InvalidToken(t *testing.T) {
+	tr := &mockTraverser{}
+	bufferedMapKey = ""
+	state, err := handleExpectEscaping([]rune("key"), 'a', tr)
+	assert.Error(t, err)
+	assert.EqualError(t, err, "invalid escaping token a")
+	assert.Equal(t, -1, state)
+}
diff --git a/pkg/unittest/valueutils/valueutils_test.go b/pkg/unittest/valueutils/valueutils_test.go
index 8ed35c7..4d30f7e 100644
--- a/pkg/unittest/valueutils/valueutils_test.go
+++ b/pkg/unittest/valueutils/valueutils_test.go
@@ -1,11 +1,14 @@
 package valueutils_test
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/helm-unittest/helm-unittest/internal/common"
 	. "github.com/helm-unittest/helm-unittest/pkg/unittest/valueutils"
 	"github.com/stretchr/testify/assert"
+
+	v3util "helm.sh/helm/v3/pkg/chartutil"
 )
 
 func TestGetValueOfSetPathWithSingleResults(t *testing.T) {
@@ -77,6 +80,110 @@ func TestBuildValueOfSetPath(t *testing.T) {
 	}
 }
 
+func TestBuildValueOfSetPath_V1(t *testing.T) {
+	t.Run("path is empty", func(t *testing.T) {
+		_, err := BuildValueOfSetPath(nil, "")
+		assert.Error(t, err)
+		assert.EqualError(t, err, "set path is empty")
+	})
+	t.Run("value is empty", func(t *testing.T) {
+		actual, err := BuildValueOfSetPath(nil, "some.path")
+		assert.NoError(t, err)
+		assert.Equal(t, map[string]interface{}{
+			"some": map[string]interface{}{
+				"path": nil,
+			},
+		}, actual)
+	})
+	t.Run("some path", func(t *testing.T) {
+		expected := map[string]interface{}{
+			"b": map[string]interface{}{
+				"c": map[string]interface{}{
+					"a": 1,
+					"b": map[string]interface{}{
+						"c": 2,
+					}}}}
+		val := map[string]interface{}{
+			"a": 1,
+			"b": map[string]interface{}{
+				"c": 2,
+			},
+		}
+		path := "b.c"
+		result, err := BuildValueOfSetPath(val, path)
+		assert.NoError(t, err)
+		assert.Equal(t, expected, result)
+	})
+	t.Run("path is not in data", func(t *testing.T) {
+		expected := map[string]interface{}{
+			"some": map[string]interface{}{
+				"path": map[string]interface{}{
+					"ingress": map[string]interface{}{
+						"hosts[1]": "example.local",
+					},
+				},
+			},
+		}
+		var data = map[string]interface{}{
+			"ingress": map[string]interface{}{"hosts[1]": "example.local"},
+		}
+		actual, err := BuildValueOfSetPath(data, "some.path")
+		assert.NoError(t, err)
+		assert.Equal(t, expected, actual)
+	})
+	t.Run("path is in values", func(t *testing.T) {
+		expected := map[string]interface{}{
+			"hosts": map[string]interface{}{
+				"ingress": map[string]interface{}{
+					"hosts[1]": "example.local",
+				},
+			},
+		}
+		var data = map[string]interface{}{
+			"ingress": map[string]interface{}{"hosts[1]": "example.local"},
+		}
+		actual, err := BuildValueOfSetPath(data, "hosts")
+		assert.NoError(t, err)
+		assert.Equal(t, expected, actual)
+	})
+	t.Run("property testing", func(t *testing.T) {
+		data := map[string]interface{}{"foo": "bar"}
+		cases := []struct {
+			input  map[string]interface{}
+			path   string
+			exp    map[string]interface{}
+			expStr string
+		}{
+			{
+				path:   "a.b",
+				input:  map[string]interface{}{"a": map[string]interface{}{"b": data}},
+				exp:    map[string]interface{}{"a": map[string]interface{}{"b": map[string]interface{}{"a": map[string]interface{}{"b": data}}}},
+				expStr: "map[a:map[b:map[a:map[b:map[foo:bar]]]]]",
+			},
+			{
+				path:   "a[1]",
+				input:  map[string]interface{}{"a": []interface{}{nil, data}},
+				exp:    map[string]interface{}{"a": []interface{}{interface{}(nil), map[string]interface{}{"a": []interface{}{interface{}(nil), data}}}},
+				expStr: "map[a:[<nil> map[a:[<nil> map[foo:bar]]]]]",
+			},
+			{
+				path:   "a[1].b",
+				input:  map[string]interface{}{"a": []interface{}{nil, map[string]interface{}{"b": data}}},
+				exp:    map[string]interface{}{"a": []interface{}{interface{}(nil), map[string]interface{}{"b": map[string]interface{}{"a": []interface{}{interface{}(nil), map[string]interface{}{"b": data}}}}}},
+				expStr: "map[a:[<nil> map[b:map[a:[<nil> map[b:map[foo:bar]]]]]]]",
+			},
+		}
+		for _, test := range cases {
+			t.Run(fmt.Sprintf("path %s and values '%v", test.path, test.input), func(t *testing.T) {
+				actual, err := BuildValueOfSetPath(test.input, test.path)
+				assert.NoError(t, err)
+				assert.Equal(t, test.exp, actual)
+				assert.Equal(t, test.expStr, fmt.Sprintf("%v", actual))
+			})
+		}
+	})
+}
+
 func TestBuildValueSetPathError(t *testing.T) {
 	a := assert.New(t)
 	data := map[string]interface{}{"foo": "bar"}
@@ -94,3 +201,379 @@ func TestBuildValueSetPathError(t *testing.T) {
 		a.EqualError(err, expect)
 	}
 }
+
+func TestMergeValues(t *testing.T) {
+	a := assert.New(t)
+	dest := map[string]interface{}{
+		"a": map[string]interface{}{
+			"b":   []interface{}{"_", map[string]interface{}{"c": "yes"}},
+			"e.f": "false",
+		},
+	}
+	src := map[string]interface{}{
+		"a": map[string]interface{}{
+			"b":   []interface{}{"_", map[string]interface{}{"c": "no"}, "a"},
+			"d":   "no",
+			"e.f": "yes",
+		},
+	}
+	expected := map[string]interface{}{
+		"a": map[string]interface{}{
+			"b":   []interface{}{"_", map[string]interface{}{"c": "no"}, "a"},
+			"d":   "no",
+			"e.f": "yes",
+		},
+	}
+	actual := v3util.MergeTables(dest, src)
+	a.Equal(expected, actual)
+}
+
+func TestMergeValues_Cases(t *testing.T) {
+	t.Run("SimpleMerge", func(t *testing.T) {
+		dest := map[string]interface{}{"a": 1, "b": 2}
+		src := map[string]interface{}{"c": 3, "d": 4}
+		expected := map[string]interface{}{"a": 1, "b": 2, "c": 3, "d": 4}
+		result := v3util.MergeTables(dest, src)
+		assert.Equal(t, expected, result)
+	})
+
+	t.Run("OverwriteExistingValue", func(t *testing.T) {
+		dest := map[string]interface{}{"a": 1}
+		src := map[string]interface{}{"a": 2}
+		expected := map[string]interface{}{"a": 2}
+		result := v3util.MergeTables(dest, src)
+		assert.Equal(t, expected, result)
+	})
+
+	t.Run("MergeNestedMaps", func(t *testing.T) {
+		dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+		src := map[string]interface{}{"a": map[string]interface{}{"c": 2}}
+		expected := map[string]interface{}{"a": map[string]interface{}{"b": 1, "c": 2}}
+		result := v3util.MergeTables(dest, src)
+		assert.Equal(t, expected, result)
+	})
+
+	t.Run("OverwriteNestedMap", func(t *testing.T) {
+		dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+		src := map[string]interface{}{"a": 2}
+		expected := map[string]interface{}{"a": 2}
+		result := v3util.MergeTables(dest, src)
+		assert.Equal(t, expected, result)
+	})
+
+	t.Run("MergeComplexMaps", func(t *testing.T) {
+		dest := map[string]interface{}{
+			"a": 1,
+			"b": map[string]interface{}{
+				"c": 2,
+			},
+		}
+		src := map[string]interface{}{
+			"a": 3,
+			"b": map[string]interface{}{
+				"d": 4,
+			},
+			"e": 5,
+		}
+		expected := map[string]interface{}{
+			"a": 3,
+			"b": map[string]interface{}{
+				"c": 2,
+				"d": 4,
+			},
+			"e": 5,
+		}
+		result := v3util.MergeTables(dest, src)
+		assert.Equal(t, expected, result)
+	})
+}
+
+func TestMergeValues_YamlValues(t *testing.T) {
+	t.Run("first", func(t *testing.T) {
+		yamlDst := `
+a:
+  b:
+   foo: bar
+`
+		yamlSrc := `
+a:
+  hosts[0]: abrakadabra
+`
+		expected := `
+a:
+  b:
+   foo: bar
+  hosts[0]: abrakadabra
+`
+		var dataDst map[string]interface{}
+		common.YmlUnmarshalTestHelper(yamlDst, &dataDst, t)
+		var dataSrc map[string]interface{}
+		common.YmlUnmarshalTestHelper(yamlSrc, &dataSrc, t)
+
+		output := v3util.MergeTables(dataDst, dataSrc)
+		out, _ := common.YmlMarshall(&output)
+		assert.YAMLEq(t, expected, out)
+	})
+	t.Run("second", func(t *testing.T) {
+		yamlDst := `
+a:
+  b:
+   hosts:
+   - foo
+   - bar
+`
+		yamlSrc := `
+a:
+  b:
+   hosts[0]: abrakadabra
+`
+		expected := `
+a:
+  b:
+   hosts:
+   - foo
+   - bar
+   hosts[0]: abrakadabra
+`
+		var dataDst map[string]interface{}
+		common.YmlUnmarshalTestHelper(yamlDst, &dataDst, t)
+		var dataSrc map[string]interface{}
+		common.YmlUnmarshalTestHelper(yamlSrc, &dataSrc, t)
+
+		output := v3util.MergeTables(dataDst, dataSrc)
+		out := common.YmlMarshallTestHelper(&output, t)
+		assert.YAMLEq(t, expected, out)
+	})
+}
+
+func TestGetValueOfSetPath(t *testing.T) {
+	t.Run("invalid-path", func(t *testing.T) {
+		yml := `
+kind: Deployment
+metadata:
+  name: my-deployment
+`
+		var dataDst map[string]interface{}
+		common.YmlUnmarshalTestHelper(yml, &dataDst, t)
+
+		actual, err := GetValueOfSetPath(dataDst, "invalid.path")
+		assert.NoError(t, err)
+		assert.Empty(t, actual)
+	})
+	t.Run("valid-path", func(t *testing.T) {
+		yml := `
+kind: Deployment
+metadata:
+  name: my-deployment
+`
+		var dataDst map[string]interface{}
+		common.YmlUnmarshalTestHelper(yml, &dataDst, t)
+
+		actual, err := GetValueOfSetPath(dataDst, "metadata.name")
+		assert.NoError(t, err)
+		assert.Equal(t, []interface{}{"my-deployment"}, actual)
+	})
+}
+
+func TestBuildValueOfSetPath_EmptyPath(t *testing.T) {
+	_, err := BuildValueOfSetPath(nil, "")
+	assert.Error(t, err)
+	assert.EqualError(t, err, "set path is empty")
+}
+
+func TestBuildValueOfSetPath_ValidPath(t *testing.T) {
+	data := map[string]interface{}{"foo": "bar"}
+	expected := map[string]interface{}{"a": map[string]interface{}{"b": data}}
+	actual, err := BuildValueOfSetPath(data, "a.b")
+	assert.NoError(t, err)
+	assert.Equal(t, expected, actual)
+}
+
+func TestBuildValueOfSetPath_InvalidToken(t *testing.T) {
+	data := map[string]interface{}{"foo": "bar"}
+	_, err := BuildValueOfSetPath(data, "{")
+	assert.Error(t, err)
+	assert.EqualError(t, err, "invalid token found {")
+}
+
+func TestBuildValueOfSetPath_UnexpectedEnd(t *testing.T) {
+	data := map[string]interface{}{"foo": "bar"}
+	_, err := BuildValueOfSetPath(data, "a[")
+	assert.Error(t, err)
+	assert.EqualError(t, err, "unexpected end of")
+}
+
+func TestBuildValueOfSetPath_NestedPath(t *testing.T) {
+	data := map[string]interface{}{"foo": "bar"}
+	expected := map[string]interface{}{
+		"a": map[string]interface{}{
+			"b": map[string]interface{}{
+				"c": data,
+			},
+		},
+	}
+	actual, err := BuildValueOfSetPath(data, "a.b.c")
+	assert.NoError(t, err)
+	assert.Equal(t, expected, actual)
+}
+
+// merge values
+func TestMergeValues_EmptySource(t *testing.T) {
+	dest := map[string]interface{}{"a": 1}
+	src := map[string]interface{}{}
+	expected := map[string]interface{}{"a": 1}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_EmptyDestination(t *testing.T) {
+	dest := map[string]interface{}{}
+	src := map[string]interface{}{"a": 1}
+	expected := map[string]interface{}{"a": 1}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_NilSource(t *testing.T) {
+	dest := map[string]interface{}{"a": 1}
+	var src map[string]interface{}
+	expected := map[string]interface{}{"a": 1}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_NilDestination(t *testing.T) {
+	dest := map[string]interface{}{}
+	src := map[string]interface{}{"a": 1}
+	expected := map[string]interface{}{"a": 1}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_OverwriteWithNonMap(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	src := map[string]interface{}{"a": 2}
+	expected := map[string]interface{}{"a": 2}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_DeepMerge(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	src := map[string]interface{}{"a": map[string]interface{}{"c": 2}}
+	expected := map[string]interface{}{"a": map[string]interface{}{"b": 1, "c": 2}}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_ComplexMerge(t *testing.T) {
+	dest := map[string]interface{}{
+		"a": 1,
+		"b": map[string]interface{}{
+			"c": 2,
+		},
+	}
+	src := map[string]interface{}{
+		"a": 3,
+		"b": map[string]interface{}{
+			"d": 4,
+		},
+		"e": 5,
+	}
+	expected := map[string]interface{}{
+		"a": 3,
+		"b": map[string]interface{}{
+			"c": 2,
+			"d": 4,
+		},
+		"e": 5,
+	}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+// new
+func TestMergeValues_KeyExistsButNotMap(t *testing.T) {
+	dest := map[string]interface{}{
+		"a": 1,
+	}
+	src := map[string]interface{}{
+		"a": map[string]interface{}{"b": 2},
+	}
+	expected := map[string]interface{}{
+		"a": map[string]interface{}{"b": 2},
+	}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_KeyExistsAndIsMap(t *testing.T) {
+	dest := map[string]interface{}{
+		"a": map[string]interface{}{"b": 1},
+	}
+	src := map[string]interface{}{
+		"a": map[string]interface{}{"c": 2},
+	}
+	expected := map[string]interface{}{
+		"a": map[string]interface{}{"b": 1, "c": 2},
+	}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_EmptySourceAndDestination(t *testing.T) {
+	dest := map[string]interface{}{}
+	src := map[string]interface{}{}
+	expected := map[string]interface{}{}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_SourceWithNilValue(t *testing.T) {
+	dest := map[string]interface{}{"a": 1}
+	src := map[string]interface{}{"b": nil}
+	expected := map[string]interface{}{"a": 1, "b": nil}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_DestinationWithNilValue(t *testing.T) {
+	dest := map[string]interface{}{"a": nil}
+	src := map[string]interface{}{"b": 2}
+	expected := map[string]interface{}{"a": nil, "b": 2}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_NestedMapWithEmptyMap(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	src := map[string]interface{}{"a": map[string]interface{}{}}
+	expected := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_EmptyNestedMap(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{}}
+	src := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	expected := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_OverwriteWithEmptyMap(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	src := map[string]interface{}{"a": map[string]interface{}{}}
+	// expected := map[string]interface{}{"a": map[string]interface{}{}}
+	expected := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
+
+func TestMergeValues_OverwriteWithNil(t *testing.T) {
+	dest := map[string]interface{}{"a": map[string]interface{}{"b": 1}}
+	src := map[string]interface{}{"a": nil}
+	expected := map[string]interface{}{"a": nil}
+	actual := v3util.MergeTables(dest, src)
+	assert.Equal(t, expected, actual)
+}
diff --git a/test/data/v3/basic/templates/configmap.yaml b/test/data/v3/basic/templates/configmap.yaml
index 163d7e5..79cdd5f 100644
--- a/test/data/v3/basic/templates/configmap.yaml
+++ b/test/data/v3/basic/templates/configmap.yaml
@@ -24,3 +24,10 @@ data:
   special.array:
     - phony-service.phony:graphql
   my.camelcase: {{ .Values.configTests.camelcaseValue | camelcase }}
+  {{- if .Values.ingress.hosts }}
+  my.ingress.hosts:
+  {{- range $host := .Values.ingress.hosts }}
+  - {{ $host }}
+  {{- end -}}
+  {{- end -}}
+
diff --git a/test/data/v3/basic/templates/pdp.yaml b/test/data/v3/basic/templates/pdp.yaml
new file mode 100644
index 0000000..36d6ef5
--- /dev/null
+++ b/test/data/v3/basic/templates/pdp.yaml
@@ -0,0 +1,7 @@
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: {{ template "basic.fullname" . }}
+spec:
+  minAvailable: 2
+  unhealthyPodEvictionPolicy: AlwaysAllow
diff --git a/test/data/v3/basic/tests/configmap_test.yaml b/test/data/v3/basic/tests/configmap_test.yaml
index 6977c49..d892b4f 100644
--- a/test/data/v3/basic/tests/configmap_test.yaml
+++ b/test/data/v3/basic/tests/configmap_test.yaml
@@ -4,6 +4,10 @@ templates:
 chart:
   appVersion: 1.0.1
   version: 0.1.0
+set:
+  # issue https://github.com/helm-unittest/helm-unittest/issues/471
+  ingress:
+    hosts[1]: override-example-global-1.local
 tests:
   - it: should NOT configure ssl params if NOT set to be exposed
     asserts:
@@ -83,3 +87,83 @@ tests:
       - equal:
           path: data["my.camelcase"]
           value: Camelcase
+
+  - it: should validate ingress hosts with root values
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - chart-example.local
+
+  - it: should override ingress hosts with set when array
+    set:
+      ingress:
+        hosts:
+        - override-example.local
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - override-example.local
+
+  - it: should patch ingress hosts
+    set:
+      ingress.hosts[0]: override-example-patch-0.local
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - override-example-patch-0.local
+
+# issue https://github.com/helm-unittest/helm-unittest/issues/471
+  - it: should patch ingress hosts with append.
+    set:
+      ingress.hosts[1]: override-example-patch-1.local
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - null
+            - override-example-patch-1.local
+
+  - it: "should have values set from file"
+    values:
+    - values/hosts.yaml
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - chart-example-first.local
+            - chart-example-second.local
+            - chart-example-third.local
+
+# issue https://github.com/helm-unittest/helm-unittest/issues/471
+  - it: "should have values set from file and patch second host"
+    values:
+    - values/hosts.yaml
+    set:
+      ingress.hosts[1]: override-example-patch-1.local
+    asserts:
+      - exists:
+          path: data["my.ingress.hosts"]
+      - equal:
+          path: data["my.ingress.hosts"]
+          value:
+            - null
+            - override-example-patch-1.local
+      - notEqual:
+          path: data["my.ingress.hosts"]
+          value:
+            - chart-example-first.local
+            - override-example-patch-1.local
+            - chart-example-third.local
diff --git a/test/data/v3/basic/tests/pdp_test.yaml b/test/data/v3/basic/tests/pdp_test.yaml
new file mode 100644
index 0000000..9311a2d
--- /dev/null
+++ b/test/data/v3/basic/tests/pdp_test.yaml
@@ -0,0 +1,11 @@
+suite: test pod disruption budget
+templates:
+  - templates/pdp.yaml
+tests:
+  - it: should pass all kinds of assertion
+    asserts:
+      - exists:
+          path: spec.minAvailable
+      - greaterOrEqual:
+          path: spec.minAvailable
+          value: 2
diff --git a/test/data/v3/basic/tests/values/hosts.yaml b/test/data/v3/basic/tests/values/hosts.yaml
new file mode 100644
index 0000000..40dd15c
--- /dev/null
+++ b/test/data/v3/basic/tests/values/hosts.yaml
@@ -0,0 +1,5 @@
+ingress:
+  hosts:
+    - chart-example-first.local
+    - chart-example-second.local
+    - chart-example-third.local