Learn Chef - Test Kitchen & Integration Testing
Series/Learn Chef/Episode 18
Episode 18 of 23

Learn Chef - Test Kitchen & Integration Testing

This episode covers Test Kitchen: the kitchen.yml structure with driver, provisioner, and suites, the converge-verify-destroy flow, various drivers from Docker to cloud, writing InSpec verification inside Kitchen, and integrating cookbook testing into the CI pipeline.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 17 you wrapped an application into a Habitat artifact with hab pkg build. But building an artifact is only half the story — before your cookbook touches a production node, it must be proven to work in a controlled environment. In episode 15 we briefly touched on test-driven infrastructure; episode 18 turns it into real practice with Test Kitchen: the standard Chef Workstation tool for testing cookbooks against real instances, from local containers to cloud nodes.

What Is Test Kitchen

Test Kitchen is a testing framework that spins up real instances (not mocks), applies the cookbook to that instance (converge), then verifies the result (verify). The cycle is deliberately kept short so feedback is fast:

  • create — spins up an instance from a driver (container, VM, or cloud node).
  • converge — runs chef-client with the cookbook under test.
  • verify — runs the verifier (InSpec) to check the resulting state.
  • destroy — deletes the instance, keeping the environment clean.

This pattern changes the way you work: instead of "write a cookbook then try it manually", you write the expectations first and let Kitchen prove them again and again. Kitchen is already bundled with Chef Workstation, so kitchen version is ready to use.

Anatomy of kitchen.yml

All of Kitchen's behavior is controlled through the kitchen.yml file at the cookbook root. This file defines three major parts: the driver (where instances run), the provisioner (how the cookbook is applied), and the suites (the scenarios being tested).

kitchen.yml
---
driver:
  name: docker
 
provisioner:
  name: chef_zero
 
platforms:
  - name: ubuntu-22.04
    driver_config:
      image: dokken/ubuntu-22.04
 
suites:
  - name: default
    run_list:
      - recipe[webserver::default]
    verifier:
      name: inspec

Let's break it down part by part. The docker driver uses the dokken/ubuntu-22.04 image, which is specially designed for Kitchen — lean and without built-in systemd. The chef_zero provisioner uses the cookbook directly from the working directory without needing a remote server, and the default suite sets a run_list pointing at the recipe under test. The inspec verifier tells Kitchen which verification to use after converge.

Choosing a Driver

The driver determines where instances run, and this choice affects both speed and how closely it matches production:

DriverSpeedProduction similarityUse case
DockerVery fastLow-mediumDaily testing, CI
VagrantMediumMediumLocal VMs with VirtualBox/VMware
Cloud (AWS, GCP, Azure)SlowHighRelease candidates, golden images

Note

Docker containers do not run systemd, so the service resource cannot be managed like it is on a full node. For cookbooks that manage services, use the dokken images that ship with a supervisor, or switch to the Vagrant driver for more realistic verification.

To change drivers, simply replace the driver.name value and adjust driver_config. For cloud, Kitchen uses plugins such as kitchen-ec2, which read credentials from the environment — credentials must never be written into kitchen.yml.

The Converge, Verify, Destroy Cycle

The main Kitchen commands follow the basic cycle. To illustrate, let's spin up an instance and apply the cookbook:

kitchen create
kitchen converge webserver-default
kitchen list

kitchen list shows the state of all instances (created, converged, or destroyed). After converge, verify with InSpec. If verification fails, fix the cookbook and run kitchen converge && kitchen verify again — this iteration is the heartbeat of test-based development. Once you are satisfied, clean up the environment:

destroy.sh
kitchen destroy

For one full round in a single command, use kitchen test, which runs create, converge, verify, and destroy in sequence — exactly the flow CI wants.

Writing InSpec Verification inside Kitchen

Verification is written as a simple InSpec profile in the test/integration/<suite-name>/ directory. InSpec resources work declaratively: describe checks a property and it asserts the expectation.

default_test.rb
describe package('nginx') do
  it { should be_installed }
end
 
describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end
 
describe port(80) do
  it { should be_listening }
end
 
describe file('/etc/nginx/nginx.conf') do
  it { should exist }
  it { should be_owned_by 'root' }
end

This verification reads facts directly from the converged instance — not from promises in the cookbook. If a recipe only installs the package without starting the service, the should be_running check will fail and force you to fix it. That is the real goal: expectations are written first, code is forced to satisfy them.

Tip

Name suites after scenarios, not just "default": nginx-basic, php-fpm-hardened, or app-multinode. The suite name determines the instance name and the verification directory, so different scenarios do not overwrite each other.

Integration with CI

Kitchen's full power appears when it runs automatically in CI: every pull request that changes a cookbook is tested against real instances before review. With the Docker driver, no special machines are needed — the runner just executes the standard commands.

ci-test.yml
name: test-cookbook
 
on:
  pull_request:
 
jobs:
  kitchen:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Chef Workstation
        run: |
          curl -L https://omnitruck.chef.io/install.sh | sudo bash -s -- -P chef-workstation
      - name: Test Kitchen
        run: kitchen test

For Jenkins, the pattern is the same — a job is run by an agent with Docker and Chef Workstation installed, then executes kitchen test as the final step. The key to CI success: make sure kitchen.yml does not depend on local paths, and give it an explicit Docker image so it is deterministic.

Conclusion

Episode 18 closes the development cycle with Test Kitchen as the bridge between writing and proving: kitchen.yml as the three-part contract, the driver as the choice of battlefield, the converge-verify-destroy flow as the working rhythm, InSpec verification as the judge, and CI as the permanent guardian.

Key takeaways:

  • kitchen.yml is the single source of truth for test configuration — driver, provisioner, and suites are all read from here.
  • The short cycle — create, converge, verify, destroy — minimizes the distance between change and feedback.
  • InSpec verification writes expectations first and makes cookbooks prove their promises on real instances.
  • Integrated CI turns testing from an individual habit into a gate no pull request can bypass.

In episode 19 we will move from "how to test" to "how to operate": performance & troubleshooting — analyzing chef-client runs, scheduling with a systemd timer or cron, caching, and solutions to common problems in the field. See you there!

Learn Chef - Test Kitchen & Integration Testing | Learn Chef