Test an Action
Test outcomes and make sure that read-only runs leave project files unchanged.
Test your action in a temporary project and inspect its outcome. The
rakko-test-utils helpers also detect file changes when a run
declares that it only reads the project.
This guide tests check-readme from Build an Action.
You need an action to test, but no harness.
Add the Test Dependencies
In your action crate, add rakko-test-utils, tempfile for temporary
projects, and Tokio to run asynchronous tests:
cargo add --dev rakko-test-utils tempfile
cargo add --dev tokio --features macros,rt
Choose a release of rakko-test-utils that uses the same release of rakko
as your action. Otherwise, the helpers expect a different Action trait and
the tests do not compile.
Test a Run
Put the tests in tests/, such as tests/check_readme.rs. Start the file with
these imports:
use std::fs;
use otter_check_readme::{CheckReadme, CheckReadmeArgs};
use rakko::{Access, Action, Args, ArgsValues, ArgumentValue, Context, Outcome, argument_name};
use rakko_test_utils::run_checking_access;
A test creates a project in a temporary directory, runs the action there, and checks the outcome:
#[tokio::test]
async fn run_without_readme_fails() {
let project = tempfile::tempdir().unwrap();
let context = Context::builder().root(project.path()).build();
let outcome = run_checking_access(&CheckReadme, &context, &CheckReadmeArgs::default()).await;
assert!(matches!(outcome, Outcome::Failed { .. }));
}
Context uses the temporary directory as the project root.
run_checking_access runs the action with your context and arguments, then
returns its outcome. The action is asynchronous, so #[tokio::test] supplies
the runtime that the test needs.
To test a project that passes, create its files before the run:
#[tokio::test]
async fn run_with_readme_passes() {
let project = tempfile::tempdir().unwrap();
fs::write(project.path().join("README.md"), "# Otter\n").unwrap();
let context = Context::builder().root(project.path()).build();
let outcome = run_checking_access(&CheckReadme, &context, &CheckReadmeArgs::default()).await;
assert!(matches!(outcome, Outcome::Passed { .. }));
}Test With Arguments
The fields of CheckReadmeArgs are private. Use from_values to construct
arguments through the same interface as the command line:
/// Returns the values of a run with `--fix`, as the command line gives them
fn values_with_fix() -> ArgsValues {
ArgsValues::new([(argument_name!("fix"), ArgumentValue::new("true"))])
}
A boolean flag that the user gave has the value true. The test then runs the
action with these arguments:
#[tokio::test]
async fn run_with_fix_writes_readme() {
let project = tempfile::tempdir().unwrap();
let context = Context::builder().root(project.path()).build();
let args = CheckReadmeArgs::from_values(&values_with_fix()).unwrap();
run_checking_access(&CheckReadme, &context, &args).await;
assert!(project.path().join("README.md").exists());
}
Test access directly. The method reads no files and starts no tools:
#[test]
fn access_with_fix_is_write() {
let project = tempfile::tempdir().unwrap();
let context = Context::builder().root(project.path()).build();
let args = CheckReadmeArgs::from_values(&values_with_fix()).unwrap();
let access = CheckReadme.access(&context, &args);
assert_eq!(access, Access::Write);
}The Access Check
run_checking_access reads the access declaration before it runs the action.
For a read-only run, it compares project files before and after the run.
Added, removed, or changed files fail the test, and the message lists them.
For a run that declares a write, the helper returns the outcome without
comparing files.
The comparison excludes these files and directories at every depth:
- Directories named
.git. - Directories named
target. - Files whose names end in
.tsbuildinfo.
The harness trusts access declarations and can run readers at the same time. If an action writes after declaring a read, it can change another action’s input. Use this helper to catch those changes in tests.
Test an Action That Runs a Tool
A test can inspect a tool declaration without installing the tool:
use otter_check_toml::CheckToml;
use rakko::{Action, RequiredVersion, ToolVersion};
use rakko_tool::MiseFile;
#[test]
fn tools_pins_the_version_of_the_mise_file() {
let pin = MiseFile::new(include_str!("../mise.rakko.toml")).pinned_version("taplo");
let tools = CheckToml.tools();
assert_eq!(
tools[0].version(),
&RequiredVersion::Exact(ToolVersion::new(pin))
);
}
This test reads the version from the same mise file as the action. So it still passes when Renovate updates the pin.
A test that runs the tool needs the tool at the version that your crate pins. Install it in the root of the crate before you run the tests:
MISE_ENV=rakko mise install
run_checking_access calls your action directly and bypasses the platform
skip. If the tool has no build for the test platform, the action returns an
error. Limit tests that run the tool to its supported platforms.
Run a Tool From an Action explains platform declarations.
Compare Paths
Windows uses different path separators from macOS and Linux. Use path to
convert a literal such as src/lib.rs to the platform’s path format. Use
path_text when you need that path as text, for example in an expected tool
report.
Run the Tests
Run the tests from your action crate:
cargo test