Ludovic Alarcon

Ludovic Alarcon .

Kubernetes and Cloud technologies enthusiast, DevOps believer - Golang and Dotnet Developer

Unit Test your Helm Charts

I’m writing and maintaining a good amount of Helm Charts on a daily basis. As Helm charts can contains logic in it, complexity keeps increasing over time.
The question now, is how to ensure charts don’t introduce regressions or bugs over change?
The answer is simple and we are already used to it, we need to use tests like we do for software!
In this article, we will focus on unit tests.

Helm Unittest


Setup

Thanks to the helm unit test plugin from Quintush, we can easily create unit tests using yaml syntax.

First things first, we need to install the plugin

> helm plugin install https://github.com/quintush/helm-unittest

We need then to create a folder tests under the helm chart root folder.
Now our test(s) suite file will be placed under the tests and will have the _test.yaml suffix.

.
├── charts
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── rbac.yaml
│   └── sa.yaml
├── tests
│   └── rbac_test.yaml
└── values.yaml

We will create the rbac_test.yaml with the following:

suite: service account tests
templates:
  - rbac.yaml

We are giving a description to our test suite and the list of templates yaml file we want to test.

Hands-on

Let’s take a look at our rbac.yaml file


{{- if .Values.rbac.create }}
{{- if .Values.rbac.scoped }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
rules:
  - apiGroups:
      - ""
    resources:
      - secrets
    verbs:
      - get
      - watch
      - list
{{- else }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secret-reader
rules:
  - apiGroups:
      - ""
    resources:
      - secrets
    verbs:
      - get
      - watch
      - list
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: secret-reader
subjects:
  - kind: ServiceAccount
    name: secret-reader-robot
    namespace: {{ .Release.Namespace }}
roleRef:
{{- if .Values.rbac.scoped }}
  kind: Role
{{- else }}
  kind: ClusterRole
{{- end }}
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

We have some logic to handle the fact we want or not create the role part of the RBAC and also if we want it scoped with a Role or cluster-wide with ClusterRole.

It’s time to get our hands dirty and write some tests for that file.
We don’t want introduce regressions in our future changes.

We will follow the Arrange-Act-Assert pattern (AAA pattern), that will provide a uniform structure for all tests.

suite: service account tests
templates:
  - rbac.yaml
tests:
  - it: Should render
    set:
      rbac:
        create: true
        scoped: false
    asserts:
      - hasDocuments:
          count: 2
      - isKind:
          of: ClusterRole
        documentIndex: 0
      - isKind:
          of: RoleBinding
        documentIndex: 1

The Act part of the pattern is done by rendering the chart. This is handle by the helm unittest plugin.

So, what this one is testing?
</br> For the given values:

We expect to have:

We are good to execute the helm unittest command to verify the test result

# we are in the chart directory

> helm unittest . -3

### Chart [ unit_test_demo ] .

 PASS  service account tests	tests/rbac_test.yaml

Charts:      1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshot:    0 passed, 0 total
Time:        2.297962ms

Great! We have tests the default behavior we want to our chart, now we have to also check the other path of our logic.
This time instead of overriding manually our values with the set section, we will use the values section. It will allow us to provide a values file.

  - it: Should not render role as rbac.create is false
    set:
      rbac:
        create: false
        scoped: false
    asserts:
      - hasDocuments:
          count: 1
      - isKind:
          of: RoleBinding

  - it: Should render a Role kind as rbac.scoped is true
    values:
      - ./values/scoped_values.yaml
    asserts:
      - hasDocuments:
          count: 2
      - isKind:
          of: Role
        documentIndex: 0

Nothing new in the assert part, we are just testing other possibility of our logic.

Let’s run the tests again

> helm unittest . -3

### Chart [ unit_test_demo ] .

 PASS  service account tests	tests/rbac_test.yaml

Charts:      1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshot:    0 passed, 0 total
Time:        3.383811ms

Everything is green, great!

Now, let’s check the last part of our logic

  - it: Should have a Role roleRef
    set:
      rbac:
        create: true
        scoped: true
    asserts:
      - equal:
          path: roleRef.kind
          value: Role
        documentIndex: 1

We are using the equal assert type that take the full path of the part we want to test and the desired value.

There is a lot more of assertion types, you can find the list here

Now, let’s assume someone inverted by accident the logic for the roleRef part.

> helm unittest . -3

 FAIL  service account tests	tests/rbac_test.yaml
	- Should have a Role roleRef

		- asserts[0] `equal` fail
			Template:	unit_test_demo/templates/rbac.yaml
			DocumentIndex:	0
			Path:	roleRef.kind
			Expected to equal:
				Role
			Actual:
				ClusterRole
			Diff:
				--- Expected
				+++ Actual
				@@ -1,2 +1,2 @@
				-Role
				+ClusterRole


Charts:      1 failed, 0 passed, 1 total
Test Suites: 1 failed, 0 passed, 1 total
Tests:       1 failed, 3 passed, 4 total
Snapshot:    0 passed, 0 total
Time:        4.478972ms

Error: plugin "unittest" exited with error

We get a direct feedback of:

This is sweet!

More testing

The helm unittest plugin has some other useful functionality

Conclusion

That very sweet, we are able to unit test our chart!
Next step is to add a step in our CI/CD pipeline to run the tests on every push to the repository.
That way we ensure there is no regression on our logic over changes.

The BDD style also allows everyone to have a better understanding of the logic and control flow of helm charts by only reading the tests suites.

Happy Helming!