From 0a57d5d2c950bc0c65dca0bbd4a7b7478438b884 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:57:19 +0100 Subject: [PATCH 1/2] Describe how to create temporary directories, and how to execute the CLI test with a specific current working directory (CWD). This expands on the basic examples and ensures the common needs for CLI app testing are covered. Hopefully, this will make testing more accessible to people reading the book. --- src/tutorial/testing.md | 27 ++++++++++++++++++++++++++- src/tutorial/testing/tests/cli.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/tutorial/testing.md b/src/tutorial/testing.md index aff4017..0747fa2 100644 --- a/src/tutorial/testing.md +++ b/src/tutorial/testing.md @@ -472,7 +472,7 @@ at the end of the function, the actual temporary file will automatically get deleted. ```rust,ignore -{{#include testing/tests/cli.rs:17:32}} +{{#include testing/tests/cli.rs:17:30}} ``` +## Executing tests in a temporary directory + +To fully control the behavior of your CLI app, you can use +`assert_fs::TempDir::new()` to create a temporary directory. +This is useful when you need to read and write +multiple directories or files. You can also use it as the +current working directory (CWD) when executing your CLI app, +ensuring test results are consistent and don't interfere with +other, unrelated files. + +Temporary directories allow you to create multiple child +directories and files within them, enabling more advanced +test cases. Just like for single temporary files, +all nested files and directories within temporary directories +are also cleaned up after the test is completed. + +```rust,ignore +{{#include testing/tests/cli.rs:32:56}} +``` + +You now know the core features of the crates `assert_cmd`, +`assert_fs` and `predicates` and are ready to start testing +your own CLI apps. Feel free to check out their documentation +to find more useful features. + ## What to test? While it can certainly be fun to write integration tests, diff --git a/src/tutorial/testing/tests/cli.rs b/src/tutorial/testing/tests/cli.rs index 105da79..52b5c0f 100644 --- a/src/tutorial/testing/tests/cli.rs +++ b/src/tutorial/testing/tests/cli.rs @@ -28,3 +28,29 @@ fn find_content_in_file() -> Result<(), Box> { Ok(()) } + +#[test] +fn find_content_in_file_of_tmp_dir() -> Result<(), Box> { + let cwd = assert_fs::TempDir::new()?; + + let child_dir = cwd.child("nested/child_dir"); + let child_file = child_dir.child("sample.txt"); + + child_file.write_str("The first\ntest file.\nLast line of first file.")?; + + // Files can be nested several levels within the temporary directory + assert!(child_file.path().ends_with("nested/child_dir/sample.txt")); + + cargo_bin_cmd!("grrs") + // Execute in the temporary directory + .current_dir(cwd.path()) + .arg("first") + .arg(child_file.path()) + .assert() + .success() + .stdout(predicate::str::contains( + "The first\nLast line of first file.", + )); + + Ok(()) +} From 9c9b5726ec33236f2c26fe9dc199895b6b569143 Mon Sep 17 00:00:00 2001 From: Samuel Plumppu <6125097+Greenheart@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:12:09 +0100 Subject: [PATCH 2/2] Rewrite example and test case to make it fit better with the rest of the book --- src/tutorial/testing.md | 96 +++++++++++++++++++++++++------ src/tutorial/testing/tests/cli.rs | 32 +++++------ 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/src/tutorial/testing.md b/src/tutorial/testing.md index 0747fa2..6806ea8 100644 --- a/src/tutorial/testing.md +++ b/src/tutorial/testing.md @@ -472,7 +472,7 @@ at the end of the function, the actual temporary file will automatically get deleted. ```rust,ignore -{{#include testing/tests/cli.rs:17:30}} +{{#include testing/tests/cli.rs:17:32}} ``` -## Executing tests in a temporary directory +## Executing tests in temporary directories -To fully control the behavior of your CLI app, you can use -`assert_fs::TempDir::new()` to create a temporary directory. -This is useful when you need to read and write -multiple directories or files. You can also use it as the -current working directory (CWD) when executing your CLI app, -ensuring test results are consistent and don't interfere with -other, unrelated files. +In the last test, we created a temporary file and passed it as an +_absolute file path_ to our program. While this is enough to verify +our core functionality, how could we write a test to ensure our program +also accepts _relative file paths_ like `sample.txt` as input? -Temporary directories allow you to create multiple child -directories and files within them, enabling more advanced -test cases. Just like for single temporary files, -all nested files and directories within temporary directories -are also cleaned up after the test is completed. +The relative file path should be resolved based on the current working +directory (CWD) where we execute our program, and this usually works fine +in regular directories. However, when writing a test with temporary files, +how do we know which directory where our test file was created? + +One way to implement this test is by creating a temporary directory, and +then setting it as the current working directory when executing our program. + +Just like we created temporary files, we can use `assert_fs` to create +temporary directories that are automatically deleted after the test is +finished. To clearly show the effect of setting the current directory, +and that our relative path `sample.txt` only can be resolved when we execute +the program in the same directory as the file, we will create two nested +temporary directories and a temporary file with the following structure: + +```txt +tmp_dir/ + child_dir/ + sample.txt +``` + +We can describe the test for our program with the following assertions: + +1. It **fails** to resolve the relative path `sample.txt` when executed + with `tmp_dir` as the current working directory. +2. It **succeeds** to resolve the relative path `sample.txt` when executed + with `child_dir` as the current working directory. + +To create the temporary directory and file structure we need for our test, +we start by creating a temporary directory `tmp_dir` by calling +[`assert_fs::TempDir::new()`][TempDir]. Then we create a child directory within it +called `child_dir`, in which we finally create the `sample.txt` file. + +[TempDir]: https://docs.rs/assert_fs/latest/assert_fs/struct.TempDir.html#method.new + +```rust,ignore +{{#include testing/tests/cli.rs:32:37}} +{{#include testing/tests/cli.rs:54:56}} +``` + +For our first, failing assertion, we set the current working directory to +`tmp_dir` by calling [`current_dir(&tmp_dir)`][current_dir]. This way, `sample.txt` resolves +to `tmp_dir/sample.txt` which does not exist, making the program exit with an error. + +[current_dir]: https://docs.rs/assert_cmd/latest/assert_cmd/cmd/struct.Command.html#method.current_dir + +```rust,ignore +{{#include testing/tests/cli.rs:32:33}} + // ... + +{{#include testing/tests/cli.rs:39:45}} +{{#include testing/tests/cli.rs:54:56}} +``` + +For our second, successful assertion, we instead call `current_dir(&child_dir)`. +This makes the program successfully resolve `sample.txt` to +`tmp_dir/child_dir/sample.txt` and print the expected result. + +```rust,ignore +{{#include testing/tests/cli.rs:32:33}} + // ... + +{{#include testing/tests/cli.rs:47:53}} +{{#include testing/tests/cli.rs:54:56}} +``` + +Putting it all together, the complete test case looks like this: ```rust,ignore {{#include testing/tests/cli.rs:32:56}} ``` -You now know the core features of the crates `assert_cmd`, -`assert_fs` and `predicates` and are ready to start testing -your own CLI apps. Feel free to check out their documentation -to find more useful features. +You now know the core features of the crates `assert_cmd`, `assert_fs` and +`predicates` and are ready to test your own CLI apps in ways that ensure +consistent test results and without interfering with other, unrelated files. +You can learn about more useful features in these crates by exploring +their documentation. ## What to test? diff --git a/src/tutorial/testing/tests/cli.rs b/src/tutorial/testing/tests/cli.rs index 52b5c0f..9c812e0 100644 --- a/src/tutorial/testing/tests/cli.rs +++ b/src/tutorial/testing/tests/cli.rs @@ -30,27 +30,27 @@ fn find_content_in_file() -> Result<(), Box> { } #[test] -fn find_content_in_file_of_tmp_dir() -> Result<(), Box> { - let cwd = assert_fs::TempDir::new()?; - - let child_dir = cwd.child("nested/child_dir"); - let child_file = child_dir.child("sample.txt"); - - child_file.write_str("The first\ntest file.\nLast line of first file.")?; +fn find_content_with_cwd_in_tmp_dir() -> Result<(), Box> { + let tmp_dir = assert_fs::TempDir::new()?; + let child_dir = tmp_dir.child("child_dir"); + let file = child_dir.child("sample.txt"); + file.write_str("A test\nActual content\nMore content\nAnother test")?; - // Files can be nested several levels within the temporary directory - assert!(child_file.path().ends_with("nested/child_dir/sample.txt")); + cargo_bin_cmd!("grrs") + .current_dir(&tmp_dir) + .arg("test") + .arg("sample.txt") + .assert() + .failure() + .stderr(predicate::str::contains("could not read file")); cargo_bin_cmd!("grrs") - // Execute in the temporary directory - .current_dir(cwd.path()) - .arg("first") - .arg(child_file.path()) + .current_dir(&child_dir) + .arg("test") + .arg("sample.txt") .assert() .success() - .stdout(predicate::str::contains( - "The first\nLast line of first file.", - )); + .stdout(predicate::str::contains("A test\nAnother test")); Ok(()) }