helm-unittest is a tool for unit-testing Helm charts. Helm is like M4 for writing Kubernetes manifests, which are YAML files. I prefer M4, because I consider the Go templating language an abomination (mutable data structures, unclear scoping, and so on and so forth). But it’s what’s used in the project, so I don’t really have a choice.
Getting back to unit testing: What helm-unittest does is render a chart and then evaluate some
expressions on it to check that expected values are present. This works fine for Helm charts that
are “application” charts (the regular type), but not so for “library” charts, which cannot render
manifests, and only define helper templates. As I’ve written a rather inscrutable Helm library
chart1, I wanted to unit test them.
The Setup
We have a common library chart and two application charts, as follows:
.
├── Chart.lock
├── charts
│ ├── application-chart-one
│ │ ├── charts
│ │ │ └── …
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── …
│ │ └── values.yaml
│ ├── application-chart-two
│ │ ├── charts
│ │ │ └── …
│ │ ├── Chart.yaml
│ │ ├── templates
│ │ │ └── …
│ │ └── values.yaml
│ └── common-library-chart
│ ├── Chart.yaml
│ └── templates
│ └── _helper_cart.tpl
├── Chart.yaml
└── values.yaml
There are two application charts using the common library chart.
What works
Testing the application charts that use the library chart works fine, but I’d like to keep my
testing chart separate from the deployed charts. A github
issue in the
helm-unittest repository outlined how to do this: We are basically adding a “fake” application
chart that lives outside the rest of the charts, and symlinks the common library chart into its
templates folder. We can then write unit tests to our heart’s content, and we are sure that the
“fake” application chart isn’t deployed. This will not work on Windows (as symlinks might not
survive), but seems to work fine in our CI.
The setup thus becomes:
├── toplevel-chart
│ ├── Chart.lock
│ ├── charts
│ │ ├── application-chart-one
│ │ │ ├── charts
│ │ │ │ └── …
│ │ │ ├── Chart.yaml
│ │ │ ├── templates
│ │ │ │ └── …
│ │ │ ├── tests
│ │ │ │ └── snapshot_test.yaml
│ │ │ └── values.yaml
│ │ ├── application-chart-two
│ │ │ ├── charts
│ │ │ │ └── …
│ │ │ ├── Chart.yaml
│ │ │ ├── templates
│ │ │ │ └── …
│ │ │ ├── tests
│ │ │ │ └── snapshot_test.yaml
│ │ │ └── values.yaml
│ │ └── library-chart
│ │ ├── Chart.yaml
│ │ └── templates
│ │ └── _helper_chart.tpl
│ ├── Chart.yaml
│ └── values.yaml
└── library-test-chart
├── Chart.lock
├── charts
│ └── library-chart -> ../../toplevel-chart/charts/library-chart
├── Chart.yaml
├── templates
│ └── …
└── tests
├── __snapshot__
├── test_one.yaml
├── test_two.yaml
└── test_three.yaml
We have some snapshot tests defined in the application charts (that’s the simple part). The
library-test-chart depends on the library chart via a symlink. Helm will log that it’s following a
symlink, but otherwise it works. Also, the dependency needs to be included explicitly in the test
chart’s Chart.yaml with a version for this to work.
Of course, calling “helm unittest .” in the toplevel chart now no longer runs the unit test in the library-test chart, but that can be fixed in the CI and test runner scripts.
What doesn’t work
There were some avenues I tried out that didn’t go anywhere, unfortunately.
Excluding the test chart with .helmignore
Unfortunately, excluding the test chart with .helmignore caused the whole chart to be ignored
while rendering. Apparently (and the
documentation seems misleading here),
this doesn’t just exclude stuff from packaging, but also from rendering.
Using conditions or tags
Specifying a
condition or using
tags to exclude the testing chart doesn’t work either, because helm-unittest can’t set specific
values for testing (there is a -v option for providing a values file, but that doesn’t seem to be
used during rendering). The approach still seems promising, but after looking at helm-unittest’s
code, there seems to be some involved translation, and the values file doesn’t seem to be used to
decide which charts to test.
-
Not a big hurdle, Helm charts become inscrutable after containing more than 3 lines. ↩︎