Helm tests are custom Kubernetes manifests that you can run against a deployed Helm release. They’re a powerful way to verify that your application is functioning as expected after installation or upgrade.

Here’s a simple Helm chart that deploys a basic Nginx web server:

# charts/my-nginx/Chart.yaml
apiVersion: v2
name: my-nginx
version: 0.1.0
description: A simple Nginx chart

# charts/my-nginx/values.yaml
replicaCount: 1

# charts/my-nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:

  name: {{ include "my-nginx.fullname" . }}

  labels:

    {{- include "my-nginx.labels" . | nindent 4 }}

spec:

  replicas: {{ .Values.replicaCount }}

  selector:
    matchLabels:

      {{- include "my-nginx.selectorLabels" . | nindent 6 }}

  template:
    metadata:
      labels:

        {{- include "my-nginx.selectorLabels" . | nindent 8 }}

    spec:
      containers:
        - name: nginx
          image: "nginx:latest"
          ports:
            - containerPort: 80
              name: http

# charts/my-nginx/templates/service.yaml
apiVersion: v1
kind: Service
metadata:

  name: {{ include "my-nginx.fullname" . }}

  labels:

    {{- include "my-nginx.labels" . | nindent 4 }}

spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:

    {{- include "my-nginx.selectorLabels" . | nindent 4 }}

Now, let’s add a test to verify that the Nginx pod is running and responding to HTTP requests.

# charts/my-nginx/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:

  name: {{ include "my-nginx.fullname" . }}-test-connection

  labels:

    {{- include "my-nginx.labels" . | nindent 4 }}

  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']

      args: ['{{ include "my-nginx.fullname" . }}:80']

  restartPolicy: Never

To run this test, first install the chart:

helm install my-nginx ./charts/my-nginx

Then, execute the tests:

helm test my-nginx

If the test passes, you’ll see output indicating success. If it fails, Helm will report the failure and show you the logs from the test pod.

Here’s how the Nginx deployment and service work together. The Deployment ensures that a specified number of Pod replicas are running with the Nginx container. The Service provides a stable IP address and DNS name (my-nginx in this case, derived from the release name) for the Nginx pods, allowing other services within the cluster to reach them. The test pod uses wget to attempt to connect to the Nginx service on port 80. If Nginx is running and accessible, wget will succeed, and the test pod will exit with a status code of 0.

The helm.sh/hook: test annotation is crucial. It tells Helm that this Pod should only be created when helm test is executed and that it’s a test resource. The restartPolicy: Never ensures that the test pod doesn’t try to restart if it fails; we want it to report its failure immediately.

When you run helm test my-nginx, Helm does the following:

  1. It retrieves the deployed release’s manifests.
  2. It looks for all resources annotated with helm.sh/hook: test.
  3. It creates these resources in your Kubernetes cluster.
  4. It waits for these test pods to complete.
  5. If any test pod exits with a non-zero status code, the helm test command fails.
  6. After the tests are done (either passed or failed), Helm deletes the test resources.

The wget command within the test pod is a simple way to check basic connectivity. If you need more sophisticated checks, you could use images with tools like curl, netcat, or even custom client applications. For instance, to check for a specific HTTP status code:

# charts/my-nginx/templates/tests/test-status.yaml
apiVersion: v1
kind: Pod
metadata:

  name: {{ include "my-nginx.fullname" . }}-test-status

  labels:

    {{- include "my-nginx.labels" . | nindent 4 }}

  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: curl
      image: curlimages/curl:latest

      command: ['curl', '-s', '-o', '/dev/null', '-w', '%{http_code}', '{{ include "my-nginx.fullname" . }}:80']

  restartPolicy: Never

In this test-status example, curl is used to fetch the HTTP status code and print it to stdout. The test pod will pass if curl exits successfully (which it does if it can make the request) and the output of the test pod’s command can be interpreted by the test runner. A more robust test would involve checking the actual output of curl to ensure it’s 200.

The include "my-nginx.fullname" . template function is important here. It dynamically generates the service name based on your Helm release name, ensuring your tests correctly target your deployed application even if you install multiple instances of the chart.

Beyond simple connectivity, you can use Helm tests to verify application-specific logic. For example, if your application exposes a health check endpoint, you can write a test to query it and assert that it returns a healthy status. You can also use tests to check database connectivity, message queue availability, or any other critical dependency.

The most surprising thing about Helm tests is that they are just regular Kubernetes resources with a special annotation. You can define any valid Kubernetes object as a test, including Jobs, DaemonSets, or even custom resources, as long as they are designed to run and exit. This flexibility allows for very comprehensive testing strategies.

The helm test command waits for the test pods to complete. If you have multiple test pods, they are executed in parallel by default. You can control this behavior if needed, though parallel execution is generally preferred for faster feedback.

After a successful helm upgrade or helm install, Helm automatically runs the tests. If any test fails, the upgrade or installation is rolled back by default. This automatic rollback is a powerful safety mechanism that prevents broken deployments from reaching production.

If a test fails, you can inspect the logs of the failed test pod to understand why. You can retrieve the logs using kubectl logs <test-pod-name>. The test pod name will typically include the release name and the test resource name, e.g., my-nginx-test-connection.

The next step after ensuring basic connectivity and status checks is to integrate more complex application-level assertions into your Helm tests.

Want structured learning?

Take the full Helm course →