NixOS Tests
4 min read
NixOS tests are reproducible integration tests that build one or more declarative NixOS systems, boot them in isolated machines, and drive them from a Python test script.
They are one of the strongest parts of the NixOS ecosystem: instead of only testing a package function or a config file, they can test the behavior of a whole Linux system or distributed setup.
Why they are powerful
• Whole-system testing: each test machine is a real NixOS configuration, so you can test modules, services, systemd units, networking, users, firewalls, filesystems, GUI programs, and boot behavior.
• Reproducible environments: the machines are built by Nix, so the OS closure, services, package versions, and test driver are pinned by the derivation.
• Multi-machine scenarios: a single test can define multiple machines, such as a server, client, database, proxy, or cluster.
• Scriptable orchestration: the Python test driver can start machines, wait for systemd units, run commands, inspect output, simulate crashes, take screenshots, and interact with QEMU.
• CI-friendly output: a NixOS test is itself a derivation. If it succeeds, the result can be cached like any other build artifact.
Basic shape
A test is a Nix module with machine definitions and a Python testScript.
nix
{ pkgs ? import <nixpkgs> { } }:
pkgs.testers.runNixOSTest {
name = "web-server-test";
nodes.server = { ... }: {
services.nginx.enable = true;
networking.firewall.allowedTCPPorts = [ 80 ];
};
nodes.client = { pkgs, ... }: {
environment.systemPackages = [ pkgs.curl ];
};
testScript = ''
start_all()
server.wait_for_unit("nginx.service")
client.succeed("curl --fail http://server/")
'';
}
Inside nixpkgs, tests are normally exposed through nixosTests.<name>. Outside nixpkgs, pkgs.testers.runNixOSTest is the usual entrypoint.
Machines
NixOS tests can use two kinds of machines:
QEMU virtual machines
Defined under nodes.
Use these when the test needs a real kernel, boot process, kernel modules, graphical/X11 testing, setuid behavior, systemd namespace behavior, or realistic VM-level failure simulation.
systemd-nspawn containers
Defined under containers.
Use these when the test benefits from faster startup and lower resource use, and does not need a separate kernel. Containers are useful for many service and network tests, but they cannot cover every system-level behavior that QEMU can.
Common defaults can be shared with:
• defaults — applies to both VMs and containers
• nodeDefaults — applies to QEMU nodes
• containerDefaults — applies to containers
Test driver primitives
The Python driver exposes one variable per machine name. Common methods include:
| Method | Use |
|---|---|
start_all() | Start all machines in parallel |
machine.wait_for_unit("foo.service") | Wait for a systemd unit to become active |
machine.succeed("cmd") | Run a command and require exit code 0 |
machine.fail("cmd") | Run a command and require non-zero exit code |
machine.wait_until_succeeds("cmd") | Retry a command until it passes |
machine.wait_for_open_port(80) | Wait until a TCP port is listening |
machine.shutdown() | Gracefully shut down a machine |
machine.crash() | Simulate sudden QEMU VM power loss |
machine.screenshot("screen.png") | Capture VM display output |
machine.shell_interact() | Open an interactive shell while debugging |
The driver also provides t, which exposes unittest.TestCase assertions, such as t.assertIn(...).
Running tests
For a standalone file:
shell
nix-build ./test.nix
For an in-tree NixOS test from a nixpkgs checkout:
shell
nix-build -A nixosTests.login
To inspect logs after a run:
shell
nix-store --read-log ./result
Interactive development
Interactive mode is useful when developing or debugging a test:
shell
nix-build -A driverInteractive ./test.nix
./result/bin/nixos-test-driver
Inside the driver REPL:
python
start_all()
test_script()
machine.succeed("systemctl status nginx --no-pager")
machine.shell_interact()
For tests from nixosTests, build the .driverInteractive attribute:
shell
nix-build -A nixosTests.login.driverInteractive
./result/bin/nixos-test-driver
Practical patterns
Service smoke test
• Enable the module under test.
• Wait for the service unit.
• Assert ports, files, command output, or API responses.
Client/server test
• Define nodes.server and nodes.client.
• Open the firewall on the server.
• Use the node name as a hostname from the client.
Failure/recovery test
• Start a cluster or service.
• Use machine.crash(), machine.shutdown(), or service restarts.
• Assert recovery behavior after reboot or failover.
Regression test for a package
Package derivations can link to a NixOS test through passthru.tests, often overriding the test’s service package to the package currently being built.
Requirements and caveats
• QEMU-based tests usually require KVM on Linux or Apple virtualization on macOS.
• Running Linux VM tests from macOS generally needs a Linux builder.
• Container tests require Nix daemon support for UID ranges and cgroups.
• Tests that mix containers and VMs on a shared VLAN may need /dev/net available in the sandbox.
• Successful test results are cached in the Nix store; remove the result path if you need to force a rerun.