# Tests {#sec-tests}

Home Manager includes a basic test suite and it is highly recommended to
include at least one test when adding a module. Tests are typically in
the form of \"golden tests\" where, for example, a generated
configuration file is compared to a known correct file.

It is relatively easy to create tests by modeling the existing tests,
found in the `tests` project directory.

## Writing Basic Tests {#sec-tests-basic}

Home Manager tests use the **NMT** framework, which provides
a set of assertion functions to verify that modules generate the expected files
and configurations. Tests are written as Nix expressions that define both the
Home Manager configuration and the test assertions.

### Test Structure {#sec-tests-structure}

A basic test file structure looks like:

```nix
{
  # Home Manager configuration
  programs.myprogram = {
    enable = true;
    settings = {
      option = "value";
    };
  };

  # NMT test script with assertions
  nmt.script = ''
    assertFileExists "home-files/.config/myprogram/config.toml"
    assertFileContent "home-files/.config/myprogram/config.toml" ${./expected-config.toml}
  '';
}
```

### Test Organization {#sec-tests-organization}

Tests are organized in the `tests` directory structure:

- `tests/modules/programs/myprogram/default.nix` - Lists all test cases for the module
- `tests/modules/programs/myprogram/basic-configuration.nix` - A basic test case
- `tests/modules/programs/myprogram/expected-config.toml` - Expected output file

The `default.nix` file should list all test cases:

```nix
{
  myprogram-basic-configuration = ./basic-configuration.nix;
  myprogram-empty-settings = ./empty-settings.nix;
}
```

Prefer keeping related assertions in as few test files as practical.
Exercising several cases in one evaluation keeps the test suite cheaper
to evaluate and reduces maintenance burden. Split cases into separate
files when they need incompatible module configuration, platform
conditions, expected assertion failures, or otherwise cannot share one
evaluation.

### Common NMT Assertions {#sec-tests-assertions}

NMT provides several assertion functions:

- `assertFileExists "path"` - Verify a file was created
- `assertPathNotExists "path"` - Verify a file was NOT created
- `assertFileContent "path" expected-file` - Compare file contents
- `assertFileRegex "path" "regex"` - Check file matches regex

For a full reference to the functions available in test scripts, you can look at NMT's
[bash-lib](https://git.sr.ht/~rycee/nmt/tree/master/item/bash-lib).

### Practical Examples {#sec-tests-examples}

Here are some real-world examples of common test patterns:

**Testing that a configuration file is generated:**
```nix
{
  programs.alacritty = {
    enable = true;
    settings.font.size = 12;
  };

  nmt.script = ''
    assertFileExists "home-files/.config/alacritty/alacritty.yml"
    assertFileContains "home-files/.config/alacritty/alacritty.yml" "size: 12"
  '';
}
```

**Testing that no files are created when disabled:**
```nix
{
  programs.alacritty.enable = false;

  nmt.script = ''
    assertPathNotExists "home-files/.config/alacritty"
  '';
}
```

**Testing exact file content against expected output:**
```nix
{
  programs.fastfetch = {
    enable = true;
    settings.display.color = "blue";
  };

  nmt.script =
    let
      configFile = "home-files/.config/fastfetch/config.jsonc";
    in
    ''
      assertFileExists "${configFile}"
      assertFileContent "${configFile}" ${./expected-config.jsonc}
    '';
}
```

**Testing multiple conditions in one test:**
```nix
{
  programs.myprogram = {
    enable = true;
    configFile = "custom.conf";
    extraConfig = "debug = true";
  };

  nmt.script = ''
    assertFileExists "home-files/.config/myprogram/custom.conf"
    assertFileRegex "home-files/.config/myprogram/custom.conf" "debug = true"
    assertFileRegex "home-files/.config/myprogram/custom.conf" "^# Generated by Home Manager"
  '';
}
```

### Platform-Specific Tests {#sec-tests-platform-specific}

When a module is platform-specific (Linux-only or Darwin-only), the test's `default.nix`
file should use `lib.optionalAttrs` to conditionally expose tests based on the platform.
This prevents evaluation errors on unsupported platforms during the test suite runs.

**Linux-only module tests:**
```nix
{ lib, pkgs, ... }:

lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
  rofi-valid-config = ./valid-config.nix;
  rofi-custom-theme = ./custom-theme.nix;
}
```

**Darwin-only module tests:**
```nix
{ lib, pkgs, ... }:

lib.optionalAttrs pkgs.stdenv.hostPlatform.isDarwin {
  sketchybar-basic = ./basic-configuration.nix;
  sketchybar-lua-config = ./lua-config.nix;
}
```

For cross-platform modules that have packages which need to be stubbed on Darwin,
add the package names to `tests/darwinScrublist.nix` to prevent build failures
during cross-platform test runs.

On Linux, packages are automatically scrubbed by the test infrastructure,
so tests should normally use the module's default package. Use
`test.stubs` or `config.lib.test.mkStubPackage` only when the automatic
scrubbing does not model the behavior that the test needs, such as a
package with additional files or a non-default executable layout.

## Using the tests command {#sec-tests-command}

Home Manager provides a convenient `tests` command for discovering and running tests:

``` shell
# List all available tests
$ nix run .#tests -- -l

# List tests matching a pattern
$ nix run .#tests -- -l alacritty

# Run all tests matching a pattern
$ nix run .#tests -- alacritty

# Run a specific test
$ nix run .#tests -- test-alacritty-empty-settings

# Run integration tests
$ nix run .#tests -- -t -l

# Interactive test selection (requires fzf)
$ python3 tests/tests.py -i

# Pass additional nix build flags
$ nix run .#tests -- alacritty -- --verbose
```

## Manual test commands {#sec-tests-manual}

For advanced usage or CI environments, you can also run tests manually using nix build commands.

The full Home Manager test suite can be run by executing

``` shell
$ nix-build --pure --option allow-import-from-derivation false tests -A build.all
```

in the project root. List all test cases through

``` shell
$ nix-build --pure tests --option allow-import-from-derivation false -A list
```

and run an individual test, for example `alacritty-empty-settings`,
through

``` shell
$ nix-build --pure tests --option allow-import-from-derivation false -A build.alacritty-empty-settings
```

However, those invocations will impurely source the system's Nixpkgs,
and may cause failures. To run against the Nixpkgs from the `flake.lock` file,
use instead e.g.

``` shell
$ nix build --option allow-import-from-derivation false .#test-all
```

or

``` shell
$ nix build --option allow-import-from-derivation false .#test-alacritty-empty-settings
```

Some tests may be marked with `enableLegacyIfd`, those may be run by run with e.g.

``` shell
$ nix-build --pure tests --arg enableLegacyIfd true -A build.mytest
```
