No need to deploy a Kubernetes cluster, perform unit tests on Helm Charts. Supports concurrent testing and allows testing only a single or a subset of files (supports glob pattern matching).
cnbcool/helm-unittest:latest
Or
docker.cnb.cool/cnb/plugins/cnbcool/helm-unittest:latest
main:
push:
ut:
stages:
- name: Execute Helm test cases
image: cnbcool/helm-unittest:latest
settings:
mode: sequential
helm_dir: my-helmchart/
tests_file:
- misc/tests/job_test.yaml
- misc/tests/client/*_test.yaml
sequential. Supports the following values. When an unsupported value is provided, it will forcibly use sequential mode.
sequential: Execute all test cases sequentially and output a complete test report.concurrent: Execute each test case concurrently, with each test case generating a separate test report. After all test cases are executed concurrently, output the test reports for each case.split: Execute all test cases sequentially and output each test report separately.glob expression matching). This property is optional and defaults to executing all test files ending with _test.yaml in the helm_dir/tests directory. This property is commonly used in Helm chart debugging or to execute only certain critical test cases, avoiding full test execution that would take too much time.This section describes how to define your own unit test cases in a YAML file.
A test suite is a collection of tests with the same purpose and scope defined in one single file. The root structure of a test suite is like below:
suite: test deploy and service
The suite property type is string. It is also an optional property. The suite name to show on test result output. For example:
PASS test deploy and service tests/deploy_and_service_test.yaml
The property values can specify attitional values files. The values files used to renders the chart, think it as the -f, --values options of helm install. ❗️The file path should be the relative path from the test suite file itself.
The values property is an array of string, optional.
For example, the following test suite specify an additional values file (values-override.yaml) which is located in the same directory as the current test file. This means that the ./values-override.yaml file and the Helm Chart's values.yaml file will be merged during rendering, with the former overriding conflicting configurations in the latter.
suite: Test Deployment
values:
- values-override.yaml
Thus, the order of elements in the values property is critical. In the following example, the values property includes 4 YAML files. During the rendering process, for overlapping configurations, the last declared file will override the previous ones.
suite: Test Deployment
values:
- a.yaml
- b.yaml
- override/c.yaml
- ../values-override.yaml
When the values property is omitted, only the values.yaml file within the Helm Chart will be used to render the chart.
The set property is used to individually configure one or more configuration items within a test suite. It's type is object of any, optional. The key is the value path with the format just like --set option of helm install, for example image.pullPolicy. The value is anything you want to set to the path specified by the key, which can be even an array or an object. This set will override values which are already set in the values file.
In the following example, the --set property defines a configuration path global.image.tag with the value latest. If the configuration item exists in the values (including both the Helm Chart's default values.yaml and any additional values files specified via values property), the set directive will override its value (i.e., global.image.tag will resolve to latest after rendering). If the configuration item does not exist, set will add it to the final configuration set.
suite: My Suite Name
set:
global.image.tag: latest
It is important to note that the declaration order of set property is irrelevant to values. For example, the following two configurations produce identical rendering results: set definitions will consistently override values specified in values files, regardless of their order.
suite: My Suite Name
set:
global.image.tag: latest
values:
- ../values-override.yaml
suite: My Suite Name
values:
- ../values-override.yaml
set:
global.image.tag: latest
The set property supports multiple configuration paths, such as:
suite: My Suite Name
set:
global.image.tag: latest
group:
enable: false
mysql.args:
- args1
- args2
💡A recommended best practice is to utilize values property (e.g., creating an
additional_valuesdirectory undertests/to store configuration collections for specific scenarios liketests/additional_values/suite1_values.yaml) to encapsulate test-specific configurations, avoiding excessivesetdeclarations in test suite.suite: My Suite Name values: - additional_values/suite1_values.yaml
The property templates is an array of string, recommended. The template files scope to test in this suite. Only the selected files will be rendered. Template files that are put in a templates sub-folder can be addressed with a linux path separator. Also the templates/ can be omitted. Using wildcards it is possible to test multiple templates without listing them one-by-one. Partial templates (which are prefixed with and _ or have the .tpl extension) are added automatically even if it is in a templates sub-folder, you don't need to add them.
For example, the following suite used to test templates/redis-test/stateful_set.yamll and templates/deployment.yaml files.
suite: Test StatefulSet
templates:
- redis-test/stateful_set.yaml
- deployment.yaml
You can use the release property to define the {{ .Release }} object. It is an optional property. There are four properties in release:
name is an optional string. It defines the release name, default to "RELEASE-NAME".
For example, the following suite sets the release name to cnb.
suite: Test StatefulSet
release:
name: cnb
namespace is an optional string, It means the namespace which release be installed to, default to "NAMESPACE".
For example, the following suite sets the release namespace to cnb.
suite: Test StatefulSet
release:
namespace: cnb
The revision of current build, default to 0. It is an optional integer.
💡 Although
release.revisionshould be a natural number, declaring it as a negative value in test cases does not cause an error.
For example, the following suite sets the revision to 1.
suite: Test StatefulSet
release:
revision: 1
release.upgrade is an optional boolean. It means whether the build is an upgrade, default to false.
For example, the following suite is an upgrade deployment, not installation.
suite: Test StatefulSet
release:
upgrade: true
There is an optional object property capabilities used to define the {{ .Capabilities }} object. It has 3 properties:
capabilities.majorVersion is an optional integer. It represents the kubernetes major version (not helm), default to the major version which is set by helm.
The following code set to the major version of kubernetes is 1 (means v1.xxx):
suite: Test StatefulSet
capabilities:
majorVersion: 1
capabilities.minorVersion is an optional integer. It represents the kubernetes minor version (not helm), default to the major version which is set by helm.
The following code set to the minor version of kubernetes is 23 (means v1.23.0):
suite: Test StatefulSet
capabilities:
majorVersion: 1
minorVersion: 23
💡The scenario where version settings are explicitly defined is rare. One common example is when Helm code adapts its rendering logic based on the current Kubernetes version. For instance:
(The value of global.kube_version in values is "{{ .Capabilities.KubeVersion.Version }}")
apiVersion: v1
kind: ConfigMap
metadata:
name: environment-variables
data:
author: "{{ .Values.global.author }}"
{{- $kube_version := tpl .Values.global.kube_version . }}
kube_version: "{{ $kube_version }}"
{{- if semverCompare ">=1.23.0" $kube_version }}
support_grpc_in_probe: "true"
{{- else }}
support_grpc_in_probe: "false"
{{- end }}
Based on this code, we can develop test case:
suite: test config map
capabilities:
majorVersion: 1
minorVersion: 25
templates:
- config_map.yaml
tests:
- it: Kubernetes version
asserts:
- equal:
path: data.kube_version
value: "v1.25.0"
- equal:
path: data.support_grpc_in_probe
value: "true"
Property apiVersions is an array of string, optional. A set of versions, default to the version set used by the defined kubernetes version. Defaults to the capabilities from system's Helm installation itself.
There is an optional object property chart used to define the {{ .Chart }} object. It has 2 optional properties:
Using the property chart.version (string, optional) to set the semantic version of chart. Default to the version set in the Chart.yaml.
For instance, the following code set the chart version to 1.3.0 (although the actual version is 1.0.0):
suite: test config map
chart:
version: 1.3.0
Property chart.appVersion (string, optional) means the app-version of the chart, default to the app-version set in the Chart.yaml.
For instance, the following code set the app-version to latest (although the actual app-version is 1.0.0).
suite: test config map
chart:
appVersion: latest
An optional property skip marks the test suite as having been skipped. Execution will continue at the next suite.
You must use the skip.reason property (string, required) to define the reason for skipping. (😅Otherwise, the skip will not take effect.)
The following code will skip during testing.
suite: Test ConfigMap Rendering
templates:
- templates/cm2.yaml
release:
namespace: default
skip:
reason: "WIP: Work In Process"
postRenderer is an optional object. A helm post-renderer to apply after chart rendering but before validation.
📖 What is the Post Rendering ?
Post rendering gives chart installers the ability to manually manipulate, configure, and/or validate rendered manifests before they are installed by Helm. This allows users with advanced configuration needs to be able to use tools like
kustomizeoryqto apply configuration changes without the need to fork a public chart or requiring chart maintainers to specify every last configuration option for a piece of software. There are also use cases for injecting common tools and side cars in enterprise environments or analysis of the manifests before deployment.A post renderer can be used with
install,upgrade, andtemplate. To use a post-renderer, use the--post-rendererflag with a path to the renderer executable you wish to use. For example:helm install mychart stable/wordpress --post-renderer /path/to/executableIf the path does not contain any separators, it will search in $PATH, otherwise it will resolve any relative paths to a fully qualified path.
Notice: Need Helm 3.1+
For example, there is a description YAML for config map named templates/cm2.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data:
key: "value1"
The content of test file tests/cm2_test.yaml is:
suite: Test ConfigMap Rendering
templates:
- templates/cm2.yaml
postRenderer:
cmd: "yq"
args:
- "eval"
- ".metadata.namespace=\"my-ns\""
tests:
- it: should render ConfigMap when v1 is available
asserts:
- isKind:
of: ConfigMap
- equal:
path: metadata.namespace
value: my-ns
I did not define metadata.namespace in the templates/cm2.yaml file, but by using the postRenderer property with a yq command, I added metadata.namespace to the final rendered output with the value my-ns.
From the example above, we can see that postRenderer has two properties: cmd and args.
$PATH.cmd.The test job is the base unit of testing. Your chart is rendered each time a test job run, and validated with assertions defined in the test. You can setup your values used to render the chart in the test job with external values files or directly in the test job definition.
The property tests represents the a list of test jobs. It is an object array. The tests property is required; omitting it will result in an error no tests found.
suite: Test ConfigMap
templates:
- templates/cm2.yaml
tests:
- job1
- job2
- ...
Define the name of the test with TDD style or any message you like for it property in tests array. It’s advisable to choose a meaningful name so that during maintenance, the main purpose of this test job is clearly evident.
For example, the following code defines the name "metadata info" for the first job (It sounds like we're testing whether the content under metadata meets expectations).
suite: Test ConfigMap
templates:
- templates/cm2.yaml
tests:
- it: metadata info
...
The values files used to renders the chart, think it as the -f, --values options of helm install. The file path should be the relative path from the test suite file itself. This file will override existing values set in the suite values.
For example:
suite: Test StatefulSet
values:
- values.yaml
templates:
- redis-test/stateful_set.yaml
tests:
- it: document
values:
- values2.yaml
asserts:
- hasDocuments:
count: 1
Property set which in test job used to set the values directly in suite file. The key is the value path with the format just like --set option of helm install, for example image.pullPolicy. The value is anything you want to set to the path specified by the key, which can be even an array or an object. This set will override values which are already set in the values file.
If both the top-level set property and the set property of the test Job are specified, and they modify the same configuration, the setproperty in the Job takes precedence.
For example, the final value of the test.enable in the first test job is true.
suite: Test Redis Client
set:
test.enable: false
test.resources.limits.cpu: 28
templates:
- redis-test/stateful_set.yaml
tests:
- it: document
set:
test.enable: true