diff --git a/cmd/flux/main.go b/cmd/flux/main.go
index 9295e83ea3ea7eb6daf56a62d46e666a44c10497..3ef4abacce28ad4de6c92d0a985d548bb0c19c24 100644
--- a/cmd/flux/main.go
+++ b/cmd/flux/main.go
@@ -25,6 +25,7 @@ import (
 	"github.com/spf13/cobra"
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 
+	"github.com/fluxcd/flux2/internal/utils"
 	"github.com/fluxcd/flux2/pkg/manifestgen/install"
 )
 
@@ -126,6 +127,18 @@ func NewRootFlags() rootFlags {
 	return rf
 }
 
+type rootContext struct {
+	kubeManager utils.KubeManager
+}
+
+var rootCtx = NewRootContext()
+
+func NewRootContext() rootContext {
+	var rc rootContext
+	rc.kubeManager = utils.DefaultKubeManager()
+	return rc
+}
+
 func main() {
 	log.SetFlags(0)
 	configureKubeconfig()
diff --git a/cmd/flux/testdata/trace/deployment.txt b/cmd/flux/testdata/trace/deployment.txt
new file mode 100644
index 0000000000000000000000000000000000000000..04ad939924b51dca485aac5a5387376e4c2cbb26
--- /dev/null
+++ b/cmd/flux/testdata/trace/deployment.txt
@@ -0,0 +1,26 @@
+
+Object:         deployment/podinfo
+Namespace:      podinfo
+Status:         Managed by Flux
+---
+HelmRelease:    podinfo
+Namespace:      podinfo
+Revision:       6.0.0
+Status:         Last reconciled at 2021-07-16 15:42:20 +0000 UTC
+Message:        Release reconciliation succeeded
+---
+HelmChart:      podinfo-podinfo
+Namespace:      flux-system
+Chart:          podinfo
+Version:        6.0.0
+Revision:       6.0.0
+Status:         Last reconciled at 2021-07-16 15:32:09 +0000 UTC
+Message:        Fetched revision: 6.0.0
+---
+HelmRepository: podinfo
+Namespace:      flux-system
+URL:            https://stefanprodan.github.io/podinfo
+Revision:       8411f23d07d3701f0e96e7d9e503b7936d7e1d56
+Status:         Last reconciled at 2021-07-11 00:25:46 +0000 UTC
+Message:        Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
+
diff --git a/cmd/flux/testdata/trace/deployment.yaml b/cmd/flux/testdata/trace/deployment.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..74fc5e46223ffbe49d6b379e624700566d762c05
--- /dev/null
+++ b/cmd/flux/testdata/trace/deployment.yaml
@@ -0,0 +1,129 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app.kubernetes.io/managed-by: Helm
+    app.kubernetes.io/name: podinfo
+    helm.toolkit.fluxcd.io/name: podinfo
+    helm.toolkit.fluxcd.io/namespace: podinfo
+  name: podinfo
+  namespace: podinfo
+spec:
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: podinfo
+    spec:
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: infrastructure
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: podinfo
+  namespace: podinfo
+spec:
+  chart:
+    spec:
+      chart: podinfo
+      sourceRef:
+        kind: HelmRepository
+        name: podinfo
+        namespace: flux-system
+status:
+  conditions:
+  - lastTransitionTime: "2021-07-16T15:42:20Z"
+    message: Release reconciliation succeeded
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+  helmChart: flux-system/podinfo-podinfo
+  lastAppliedRevision: 6.0.0
+  lastAttemptedRevision: 6.0.0
+  lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmChart
+metadata:
+  name: podinfo-podinfo
+  namespace: flux-system
+spec:
+  chart: podinfo
+  sourceRef:
+    kind: HelmRepository
+    name: podinfo
+  version: 6.0.0
+status:
+  artifact:
+    checksum: cf13ba96773d9a879cd052c86e73199b3f96c854
+    lastUpdateTime: "2021-08-01T04:42:55Z"
+    revision: 6.0.0
+  conditions:
+  - lastTransitionTime: "2021-07-16T15:32:09Z"
+    message: 'Fetched revision: 6.0.0'
+    reason: ChartPullSucceeded
+    status: "True"
+    type: Ready
+
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: HelmRepository
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: infrastructure
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: podinfo
+  namespace: flux-system
+spec:
+  interval: 5m
+  timeout: 1m0s
+  url: https://stefanprodan.github.io/podinfo
+status:
+  artifact:
+    checksum: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
+    lastUpdateTime: "2021-07-11T00:25:46Z"
+    revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56
+  conditions:
+  - lastTransitionTime: "2021-07-11T00:25:46Z"
+    message: 'Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56'
+    reason: IndexationSucceed
+    status: "True"
+    type: Ready
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: infrastructure
+  namespace: flux-system
+spec:
+  path: ./infrastructure/
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+  validation: client
+status:
+  conditions:
+  - lastTransitionTime: "2021-08-01T04:52:56Z"
+    message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: flux-system
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: flux-system
+  namespace: flux-system
+spec:
+  gitImplementation: go-git
+  ref:
+    branch: main
+  secretRef:
+    name: flux-system
+
diff --git a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f43f28ccf8f03da5ee141e702e3632497df6da77
--- /dev/null
+++ b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.txt
@@ -0,0 +1,19 @@
+
+Object:        HelmRelease/podinfo
+Namespace:     podinfo
+Status:        Managed by Flux
+---
+Kustomization: infrastructure
+Namespace:     flux-system
+Path:          ./infrastructure
+Revision:      main/696f056df216eea4f9401adbee0ff744d4df390f
+Status:        Last reconciled at 2021-08-01 04:52:56 +0000 UTC
+Message:       Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+---
+GitRepository: flux-system
+Namespace:     flux-system
+URL:           ssh://git@github.com/example/repo
+Revision:      main/696f056df216eea4f9401adbee0ff744d4df390f
+Status:        Last reconciled at 2021-07-20 00:48:16 +0000 UTC
+Message:       Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+
diff --git a/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..dee892aee291aeec6dc289b81acb3f6e69a1aad9
--- /dev/null
+++ b/cmd/flux/testdata/trace/helmrelease-missing-git-ref.yaml
@@ -0,0 +1,73 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: infrastructure
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: podinfo
+  namespace: podinfo
+spec:
+  chart:
+    spec:
+      chart: podinfo
+      sourceRef:
+        kind: HelmRepository
+        name: podinfo
+        namespace: flux-system
+status:
+  conditions:
+  - lastTransitionTime: "2021-07-16T15:42:20Z"
+    message: Release reconciliation succeeded
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+  helmChart: flux-system/podinfo-podinfo
+  lastAppliedRevision: 6.0.0
+  lastAttemptedRevision: 6.0.0
+  lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: infrastructure
+  namespace: flux-system
+spec:
+  path: ./infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+  validation: client
+status:
+  conditions:
+  - lastTransitionTime: "2021-08-01T04:52:56Z"
+    message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+  lastAppliedRevision: main/696f056df216eea4f9401adbee0ff744d4df390f
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: flux-system
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: flux-system
+  namespace: flux-system
+spec:
+  gitImplementation: go-git
+  secretRef:
+    name: flux-system
+  url: ssh://git@github.com/example/repo
+status:
+  artifact:
+    lastUpdateTime: "2021-08-01T04:28:42Z"
+    revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+  conditions:
+  - lastTransitionTime: "2021-07-20T00:48:16Z"
+    message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
+    reason: GitOperationSucceed
+    status: "True"
+    type: Ready
+
diff --git a/cmd/flux/testdata/trace/helmrelease.txt b/cmd/flux/testdata/trace/helmrelease.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ac075ae87107aefb478bda4123477275e1624cef
--- /dev/null
+++ b/cmd/flux/testdata/trace/helmrelease.txt
@@ -0,0 +1,20 @@
+
+Object:        HelmRelease/podinfo
+Namespace:     podinfo
+Status:        Managed by Flux
+---
+Kustomization: infrastructure
+Namespace:     flux-system
+Path:          ./infrastructure
+Revision:      main/696f056df216eea4f9401adbee0ff744d4df390f
+Status:        Last reconciled at 2021-08-01 04:52:56 +0000 UTC
+Message:       Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+---
+GitRepository: flux-system
+Namespace:     flux-system
+URL:           ssh://git@github.com/example/repo
+Branch:        main
+Revision:      main/696f056df216eea4f9401adbee0ff744d4df390f
+Status:        Last reconciled at 2021-07-20 00:48:16 +0000 UTC
+Message:       Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+
diff --git a/cmd/flux/testdata/trace/helmrelease.yaml b/cmd/flux/testdata/trace/helmrelease.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4f0db83df72d8afe7f9558875a976f7a36c5f58f
--- /dev/null
+++ b/cmd/flux/testdata/trace/helmrelease.yaml
@@ -0,0 +1,75 @@
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: infrastructure
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: podinfo
+  namespace: podinfo
+spec:
+  chart:
+    spec:
+      chart: podinfo
+      sourceRef:
+        kind: HelmRepository
+        name: podinfo
+        namespace: flux-system
+status:
+  conditions:
+  - lastTransitionTime: "2021-07-16T15:42:20Z"
+    message: Release reconciliation succeeded
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+  helmChart: flux-system/podinfo-podinfo
+  lastAppliedRevision: 6.0.0
+  lastAttemptedRevision: 6.0.0
+  lastAttemptedValuesChecksum: c31db75d05b7515eba2eef47bd71038c74b2e531
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
+kind: Kustomization
+metadata:
+  name: infrastructure
+  namespace: flux-system
+spec:
+  path: ./infrastructure
+  sourceRef:
+    kind: GitRepository
+    name: flux-system
+  validation: client
+status:
+  conditions:
+  - lastTransitionTime: "2021-08-01T04:52:56Z"
+    message: 'Applied revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
+    reason: ReconciliationSucceeded
+    status: "True"
+    type: Ready
+  lastAppliedRevision: main/696f056df216eea4f9401adbee0ff744d4df390f
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta1
+kind: GitRepository
+metadata:
+  labels:
+    kustomize.toolkit.fluxcd.io/name: flux-system
+    kustomize.toolkit.fluxcd.io/namespace: flux-system
+  name: flux-system
+  namespace: flux-system
+spec:
+  gitImplementation: go-git
+  ref:
+    branch: main
+  secretRef:
+    name: flux-system
+  url: ssh://git@github.com/example/repo
+status:
+  artifact:
+    lastUpdateTime: "2021-08-01T04:28:42Z"
+    revision: main/696f056df216eea4f9401adbee0ff744d4df390f
+  conditions:
+  - lastTransitionTime: "2021-07-20T00:48:16Z"
+    message: 'Fetched revision: main/696f056df216eea4f9401adbee0ff744d4df390f'
+    reason: GitOperationSucceed
+    status: "True"
+    type: Ready
+
diff --git a/cmd/flux/testdata/trace/no-args.txt b/cmd/flux/testdata/trace/no-args.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4f71b995b2f5eea0d6029f61cdd6a6ee6ab176dd
--- /dev/null
+++ b/cmd/flux/testdata/trace/no-args.txt
@@ -0,0 +1 @@
+object name is required
diff --git a/cmd/flux/trace.go b/cmd/flux/trace.go
index d7ee022ee2f63330d5ebe89d1fcd6246ec43e10e..c18449f131a2b638c871073922cbe84227388bb2 100644
--- a/cmd/flux/trace.go
+++ b/cmd/flux/trace.go
@@ -89,7 +89,7 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
 	defer cancel()
 
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
+	kubeClient, err := rootCtx.kubeManager.NewClient(rootArgs.kubeconfig, rootArgs.kubecontext)
 	if err != nil {
 		return err
 	}
@@ -121,7 +121,7 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
 		if err != nil {
 			return err
 		}
-		fmt.Println(report)
+		rootCmd.Print(report)
 		return nil
 	}
 
@@ -130,7 +130,7 @@ func traceCmdRun(cmd *cobra.Command, args []string) error {
 		if err != nil {
 			return err
 		}
-		fmt.Println(report)
+		rootCmd.Print(report)
 		return nil
 	}
 
diff --git a/cmd/flux/trace_test.go b/cmd/flux/trace_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e194be2a31763f6973b4e1c6e834741bbe9510f2
--- /dev/null
+++ b/cmd/flux/trace_test.go
@@ -0,0 +1,180 @@
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"context"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/fluxcd/flux2/internal/utils"
+	"github.com/google/go-cmp/cmp"
+	shellwords "github.com/mattn/go-shellwords"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func init() {
+	// Ensure tests print consistent timestamps regardless of timezone
+	os.Setenv("TZ", "UTC")
+}
+
+func readYamlObjects(objectFile string) ([]client.Object, error) {
+	obj, err := ioutil.ReadFile(objectFile)
+	if err != nil {
+		return nil, err
+	}
+	objects := []client.Object{}
+	reader := k8syaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(obj)))
+	for {
+		doc, err := reader.Read()
+		if err != nil {
+			if err == io.EOF {
+				break
+			}
+		}
+		unstructuredObj := &unstructured.Unstructured{}
+		decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(doc), len(doc))
+		err = decoder.Decode(unstructuredObj)
+		if err != nil {
+			return nil, err
+		}
+		objects = append(objects, unstructuredObj)
+	}
+	return objects, nil
+}
+
+// A KubeManager that can create objects that are subject to a test.
+type fakeKubeManager struct {
+	fakeClient client.Client
+}
+
+func (m *fakeKubeManager) NewClient(kubeconfig string, kubecontext string) (client.Client, error) {
+	return m.fakeClient, nil
+}
+
+func (m *fakeKubeManager) CreateObjects(clientObjects []client.Object) error {
+	for _, obj := range clientObjects {
+		err := m.fakeClient.Create(context.Background(), obj)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func NewFakeKubeManager() *fakeKubeManager {
+	c := fakeclient.NewClientBuilder().WithScheme(utils.NewScheme()).Build()
+	return &fakeKubeManager{
+		fakeClient: c,
+	}
+}
+
+// Run the command and return the captured output.
+func executeCommand(cmd string) (string, error) {
+	args, err := shellwords.Parse(cmd)
+	if err != nil {
+		return "", err
+	}
+
+	buf := new(bytes.Buffer)
+
+	rootCmd.SetOut(buf)
+	rootCmd.SetErr(buf)
+	rootCmd.SetArgs(args)
+
+	_, err = rootCmd.ExecuteC()
+	result := buf.String()
+
+	return result, err
+}
+
+// Structure used for each test to load objects into kubernetes, run
+// commands and assert on the expected output.
+type cmdTestCase struct {
+	// The command line arguments to test.
+	args string
+	// When true, the test expects the command to fail.
+	wantError bool
+	// Filename that contains the expected test output.
+	goldenFile string
+	// Filename that contains yaml objects to load into Kubernetes
+	objectFile string
+}
+
+func (cmd *cmdTestCase) runTestCmd(t *testing.T) {
+	km := NewFakeKubeManager()
+	rootCtx.kubeManager = km
+
+	if cmd.objectFile != "" {
+		clientObjects, err := readYamlObjects(cmd.objectFile)
+		if err != nil {
+			t.Fatalf("Error loading yaml: '%v'", err)
+		}
+		err = km.CreateObjects(clientObjects)
+		if err != nil {
+			t.Fatalf("Error creating test objects: '%v'", err)
+		}
+	}
+
+	actual, err := executeCommand(cmd.args)
+	if (err != nil) != cmd.wantError {
+		t.Fatalf("Expected error='%v', Got: %v", cmd.wantError, err)
+	}
+	if err != nil {
+		actual = err.Error()
+	}
+	contents, err := ioutil.ReadFile(cmd.goldenFile)
+	if err != nil {
+		t.Fatalf("Error reading golden file: '%s'", err)
+	}
+	expected := strings.TrimSuffix(string(contents), "\n")
+	diff := cmp.Diff(expected, actual)
+	if diff != "" {
+		t.Errorf("Mismatch from '%s' (-want +got):\n%s", cmd.goldenFile, diff)
+	}
+}
+
+func TestTraceNoArgs(t *testing.T) {
+	cmd := cmdTestCase{
+		args:       "trace",
+		wantError:  true,
+		goldenFile: "testdata/trace/no-args.txt",
+	}
+	cmd.runTestCmd(t)
+}
+
+func TestTraceDeployment(t *testing.T) {
+	cmd := cmdTestCase{
+		args:       "trace podinfo -n podinfo --kind deployment --api-version=apps/v1",
+		wantError:  false,
+		goldenFile: "testdata/trace/deployment.txt",
+		objectFile: "testdata/trace/deployment.yaml",
+	}
+	cmd.runTestCmd(t)
+}
+
+func TestTraceHelmRelease(t *testing.T) {
+	cmd := cmdTestCase{
+		args:       "trace podinfo -n podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2beta1",
+		wantError:  false,
+		goldenFile: "testdata/trace/helmrelease.txt",
+		objectFile: "testdata/trace/helmrelease.yaml",
+	}
+	cmd.runTestCmd(t)
+}
+
+func TestTraceHelmReleaseMissingGitRef(t *testing.T) {
+	cmd := cmdTestCase{
+		args:       "trace podinfo -n podinfo --kind HelmRelease --api-version=helm.toolkit.fluxcd.io/v2beta1",
+		wantError:  false,
+		goldenFile: "testdata/trace/helmrelease-missing-git-ref.txt",
+		objectFile: "testdata/trace/helmrelease-missing-git-ref.yaml",
+	}
+	cmd.runTestCmd(t)
+}
diff --git a/go.mod b/go.mod
index ed4d8972bdb1241645d775df0803b5e9308c8e8e..e89ce2e36570a45e823124b9382e4a6e2443e064 100644
--- a/go.mod
+++ b/go.mod
@@ -18,8 +18,10 @@ require (
 	github.com/fluxcd/pkg/version v0.0.1
 	github.com/fluxcd/source-controller/api v0.15.3
 	github.com/go-git/go-git/v5 v5.4.2
+	github.com/google/go-cmp v0.5.5
 	github.com/google/go-containerregistry v0.2.0
 	github.com/manifoldco/promptui v0.7.0
+	github.com/mattn/go-shellwords v1.0.12
 	github.com/olekukonko/tablewriter v0.0.4
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/pflag v1.0.5
diff --git a/go.sum b/go.sum
index ba5342d47db364657dc7c275068798eaaf6ba748..a8695f81b804b4062bd9fbeb7acdf782b173a27f 100644
--- a/go.sum
+++ b/go.sum
@@ -537,6 +537,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
+github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
diff --git a/internal/utils/utils.go b/internal/utils/utils.go
index 50b903fa06e4c78a40515d315292cbd3a3401db5..b6e1ec9024abc11660c992b451175379f6e12880 100644
--- a/internal/utils/utils.go
+++ b/internal/utils/utils.go
@@ -131,12 +131,39 @@ func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error)
 	return cfg, nil
 }
 
-func KubeClient(kubeConfigPath string, kubeContext string) (client.Client, error) {
+// KubeManger creates a Kubernetes client.Client. This interface exists to
+// facilitate unit testing and provide a fake client.
+type KubeManager interface {
+	NewClient(string, string) (client.Client, error)
+}
+
+type defaultKubeManager struct{}
+
+func DefaultKubeManager() KubeManager {
+	var manager defaultKubeManager
+	return manager
+}
+
+func (m defaultKubeManager) NewClient(kubeConfigPath string, kubeContext string) (client.Client, error) {
 	cfg, err := KubeConfig(kubeConfigPath, kubeContext)
 	if err != nil {
 		return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
 	}
 
+	scheme := NewScheme()
+	kubeClient, err := client.New(cfg, client.Options{
+		Scheme: scheme,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
+	}
+
+	return kubeClient, nil
+}
+
+// Create the Scheme, methods for serializing and deserializing API objects
+// which can be shared by tests.
+func NewScheme() *apiruntime.Scheme {
 	scheme := apiruntime.NewScheme()
 	_ = apiextensionsv1.AddToScheme(scheme)
 	_ = corev1.AddToScheme(scheme)
@@ -149,15 +176,13 @@ func KubeClient(kubeConfigPath string, kubeContext string) (client.Client, error
 	_ = notificationv1.AddToScheme(scheme)
 	_ = imagereflectv1.AddToScheme(scheme)
 	_ = imageautov1.AddToScheme(scheme)
+	return scheme
+}
 
-	kubeClient, err := client.New(cfg, client.Options{
-		Scheme: scheme,
-	})
-	if err != nil {
-		return nil, fmt.Errorf("kubernetes client initialization failed: %w", err)
-	}
-
-	return kubeClient, nil
+func KubeClient(kubeConfigPath string, kubeContext string) (client.Client, error) {
+	m := DefaultKubeManager()
+	kubeClient, err := m.NewClient(kubeConfigPath, kubeContext)
+	return kubeClient, err
 }
 
 // SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS