In addition to Unit and Integration tests, you can also write tests that interact with your application as a real user would. That technique is called End to End(E2E) testing.
In this example we have a workspace with two members:
- server: a web server that uses Axum for HTTP and Sqlx connect to an instance of PostgreSQL
- e2e: a end-to-end test "script" that drives Firefox into interacting with the sever
Quick-start an E2E project in a fresh directory with:
nix flake init -t github:ipetkov/crane#end-to-end-testing
Alternatively, if you have an existing project already, copy and paste the
following flake.nix
and modify it to build your workspace's packages:
{
description = "Example E2E testing";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane.url = "github:ipetkov/crane";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, crane, flake-utils, rust-overlay, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
inherit (pkgs) lib;
craneLib = (crane.mkLib pkgs).overrideToolchain (p: p.rust-bin.stable.latest.default);
src = craneLib.cleanCargoSource ./.;
workspace = craneLib.buildPackage {
inherit src;
doCheck = false;
nativeBuildInputs = lib.optionals pkgs.stdenv.isDarwin
(with pkgs.darwin.apple_sdk.frameworks; [
pkgs.libiconv
CoreFoundation
Security
SystemConfiguration
]);
};
# The script inlined for brevity, consider extracting it
# so that it becomes independent of nix
runE2ETests = pkgs.runCommand "e2e-tests"
{
nativeBuildInputs = with pkgs; [
retry
curl
geckodriver
firefox
cacert
postgresql
];
} ''
wait-for-connection() {
timeout 5s \
retry --until=success --delay "1" -- \
curl -s "$@"
}
initdb postgres-data
pg_ctl --pgdata=postgres-data --options "-c unix_socket_directories=$PWD" start
export DATABASE_URL="postgres:///postgres?host=$PWD"
psql "$DATABASE_URL" <<EOF
CREATE TABLE users(name TEXT);
EOF
${workspace}/bin/server &
wait-for-connection --fail localhost:8000
# Firefox likes to write to $HOME
HOME="$(mktemp -d)" geckodriver &
wait-for-connection localhost:4444
${workspace}/bin/e2e_tests
touch $out
'';
pkgsSupportsPackage = pkg:
(lib.elem system pkg.meta.platforms) && !(lib.elem system pkg.meta.badPlatforms);
in
{
checks = {
inherit workspace;
# Firefox is broken in some platforms (namely "aarch64-apple-darwin"), skip those
} // (lib.optionalAttrs (pkgsSupportsPackage pkgs.firefox) {
inherit runE2ETests;
});
devShells.default = pkgs.mkShell {
BuildInputs = with pkgs; [
rustc
cargo
] ++ (lib.optionals (!pkgs.stdenv.isDarwin) [
geckodriver
firefox
]);
};
});
}