Published on to joshleeb's blog
We all know testing is important. And, I hope, it might even be familiar. You’re coding away and catch yourself thinking “I should probably test this”. And before you know it you’ve mashed on your keyboard and there appears
#[cfg(test)]
mod tests {
...
}
These are unit tests which are super easy to setup. No added dependencies, no added tooling.
But what about integration tests?
Rust supports those too, which you probably already know. They’re only slightly more involved to setup so they’re still fairly straightforward.
But there are some caveats that I personally ran into.
For one, The Rust Book explains that
each file in the tests directory is a separate crate.
Which on its own doesn’t sound too bad. But it has the implication that having
code shared between your integration tests isn’t intuitive. I think it’s
because the code structure goes back to the Rust 2015 edition of defining
modules, using mod.rs
.
And two, which is arguably the bigger issue, is that it’s not obvious how to run only all integration tests. Or only all unit tests for that matter, but we’ll get to that too.
A Better Setup
I went looking through docs and crates and eventually opened up the source for diesel-rs/diesel, which isn’t the first time it’s been a great reference.
Now it looks like diesel has a separate crate, internal to the workspace,
called diesel_tests
which appears to contain the integration tests. But I
don’t want to setup a new crate and workspace just to extend my test suite.
Instead let’s take a look at the Cargo.toml
for that crate and borrow what
we can borrow.
For my new crate, mycrate
, I’ve setup the Cargo.toml
as
[package]
name = "mycrate"
...
autotests = false
[[test]]
name = "integration"
path = "tests/lib.rs"
Specifically, we’ve added two sections.
Autotests
The autotests = false
disables automatic test discovery. This sounds bad but
isn’t needed because we’re manually telling Cargo where to find our tests
with…
Test Target
This may look familiar if you’ve seen [[bin]]
or [[example]]
where we
explicitly specify an additional target for Cargo to work with. This is no
different. We are simply telling Cargo there’s a test target called
“integration” which has an entry point at tests/lib.rs
.
Single Integration Test Crate
This config solves the first caveat. We no longer have to deal with a crate per integration test file, and so the way we handle code shared between the tests is the same way we would handle sharing code in the implementation.
And everything is still in the tests
directory, so no changes there either.
Running Tests
We’ve also made segmenting our tests a bit clearer.
# Run all tests
cargo test
# Run only unit tests
cargo test --lib
# Run only integration tests
cargo test --test integration
# Run only integration tests, single threaded
# (you’ll probably want this one)
cargo test --test integration -- --test-threads=1
Addendum
- 2020-05-20
- u/matklad on r/rust commented that by specifying tests in
tests/integration/main.rs
Cargo will automatically discover the tests in the same way we manually specified, and so we can use the same commands as described above.
- u/matklad on r/rust commented that by specifying tests in