Writing Tests
The following focuses on writing an integration test. However, writing unit tests is also encouraged!
Testsuite
Cargo has a wide variety of integration tests that execute the cargo
binary
and verify its behavior, located in the testsuite
directory. The
support
crate contains many helpers to make this process easy.
These tests typically work by creating a temporary "project" with a
Cargo.toml
file, executing the cargo
binary process, and checking the
stdout and stderr output against the expected output.
cargo_test
attribute
Cargo's tests use the #[cargo_test]
attribute instead of #[test]
. This
attribute injects some code which does some setup before starting the test,
creating the little "sandbox" described below.
Basic test structure
The general form of a test involves creating a "project", running cargo
, and
checking the result. Projects are created with the ProjectBuilder
where
you specify some files to create. The general form looks like this:
let p = project()
.file("src/main.rs", r#"fn main() { println!("hi!"); }"#)
.build();
The project creates a mini sandbox under the "cargo integration test"
directory with each test getting a separate directory such as
/path/to/cargo/target/cit/t123/
. Each project appears as a separate
directory. There is also an empty home
directory created that will be used
as a home directory instead of your normal home directory.
If you do not specify a Cargo.toml
manifest using file()
, one is
automatically created with a project name of foo
using basic_manifest()
.
To run Cargo, call the cargo
method and make assertions on the execution:
p.cargo("run --bin foo")
.with_stderr(
"\
[COMPILING] foo [..]
[FINISHED] [..]
[RUNNING] `target/debug/foo`
",
)
.with_stdout("hi!")
.run();
This uses the Execs
struct to build up a command to execute, along with
the expected output.
See support::lines_match
for an explanation of the string pattern matching.
Patterns are used to make it easier to match against the expected output.
Browse the pub
functions in the support
crate for a variety of other
helpful utilities.
Testing Nightly Features
If you are testing a Cargo feature that only works on "nightly" Cargo, then
you need to call masquerade_as_nightly_cargo
on the process builder like
this:
p.cargo("build").masquerade_as_nightly_cargo()
If you are testing a feature that only works on nightly rustc (such as benchmarks), then you should exit the test if it is not running with nightly rust, like this:
if !is_nightly() {
// Add a comment here explaining why this is necessary.
return;
}
Platform-specific Notes
When checking output, use /
for paths even on Windows: the actual output
of \
on Windows will be replaced with /
.
Be careful when executing binaries on Windows. You should not rename, delete, or overwrite a binary immediately after running it. Under some conditions Windows will fail with errors like "directory not empty" or "failed to remove" or "access is denied".
Specifying Dependencies
You should not write any tests that use the network such as contacting crates.io. Typically, simple path dependencies are the easiest way to add a dependency. Example:
let p = project()
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "1.0.0"
[dependencies]
bar = {path = "bar"}
"#)
.file("src/lib.rs", "extern crate bar;")
.file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("bar/src/lib.rs", "")
.build();
If you need to test with registry dependencies, see
support::registry::Package
for creating packages you can depend on.
If you need to test git dependencies, see support::git
to create a git
dependency.