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:

MethodUse
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.

• Nix
• NixOS