Testing

uv2nix uses the pyproject.nix build infrastructure.

Unlike the nixpkgs, runtime & test dependencies are not available at build time. Tests should instead be implemented as separate derivations.

This usage pattern shows how to:

  • Overriding a package adding tests to passthru.tests
  • Using passthru.tests in Flake checks

flake.nix

{
  description = "Pytest flake using uv2nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

    pyproject-nix = {
      url = "github:pyproject-nix/pyproject.nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    uv2nix = {
      url = "github:pyproject-nix/uv2nix";
      inputs.pyproject-nix.follows = "pyproject-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    pyproject-build-systems = {
      url = "github:pyproject-nix/build-system-pkgs";
      inputs.pyproject-nix.follows = "pyproject-nix";
      inputs.uv2nix.follows = "uv2nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  # This example shows testing with pytest using uv2nix.
  # You should first read and understand the hello-world example before this one.

  outputs =
    {
      nixpkgs,
      uv2nix,
      pyproject-nix,
      pyproject-build-systems,
      ...
    }:
    let
      inherit (nixpkgs) lib;

      forAllSystems = lib.genAttrs lib.systems.flakeExposed;

      workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };

      overlay = workspace.mkPyprojectOverlay {
        sourcePreference = "wheel";
      };

      # Python sets grouped per system
      pythonSets = forAllSystems (
        system:
        let
          pkgs = nixpkgs.legacyPackages.${system};
          inherit (pkgs) stdenv;

          baseSet = pkgs.callPackage pyproject-nix.build.packages {
            python = pkgs.python312;
          };

          # An overlay of build fixups & test additions.
          pyprojectOverrides = final: prev: {

            # testing is the name of our example package
            testing = prev.testing.overrideAttrs (old: {

              passthru = old.passthru // {
                # Put all tests in the passthru.tests attribute set.
                # Nixpkgs also uses the passthru.tests mechanism for ofborg test discovery.
                #
                # For usage with Flakes we will refer to the passthru.tests attributes to construct the flake checks attribute set.
                tests =
                  let
                    # Construct a virtual environment with only the test dependency-group enabled for testing.
                    virtualenv = final.mkVirtualEnv "testing-pytest-env" {
                      testing = [ "test" ];
                    };

                  in
                  (old.tests or { })
                  // {
                    pytest = stdenv.mkDerivation {
                      name = "${final.testing.name}-pytest";
                      inherit (final.testing) src;
                      nativeBuildInputs = [
                        virtualenv
                      ];
                      dontConfigure = true;

                      # Because this package is running tests, and not actually building the main package
                      # the build phase is running the tests.
                      #
                      # In this particular example we also output a HTML coverage report, which is used as the build output.
                      buildPhase = ''
                        runHook preBuild
                        pytest --cov tests --cov-report html
                        runHook postBuild
                      '';

                      # Install the HTML coverage report into the build output.
                      #
                      # If you wanted to install multiple test output formats such as TAP outputs
                      # you could make this derivation a multiple-output derivation.
                      #
                      # See https://nixos.org/manual/nixpkgs/stable/#chap-multiple-output for more information on multiple outputs.
                      installPhase = ''
                        runHook preInstall
                        mv htmlcov $out
                        runHook postInstall
                      '';
                    };

                  };
              };
            });
          };

        in
        baseSet.overrideScope (
          lib.composeManyExtensions [
            pyproject-build-systems.overlays.default
            overlay
            pyprojectOverrides
          ]
        )
      );

    in
    {
      # Construct flake checks from Python set
      checks = forAllSystems (
        system:
        let
          pythonSet = pythonSets.${system};
        in
        {
          inherit (pythonSet.testing.passthru.tests) pytest;
        }
      );
    };
}

pyproject.toml

[project]
name = "testing"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"

[project.scripts]
hello = "testing:hello"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
  "ruff>=0.7.2",
  {include-group = "test"}
]
test = [
  "pytest-cov>=6.0.0",
  "pytest>=8.3.3",
]