Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
210: Integration Tests Directory Structure
A few years ago, I was reviewing a PR for a teammate who was building a complex client for a cloud storage API. He had spent the afternoon meticulously organizing his integration tests. He'd created a tests/ directory and, wanting to be clean, he created a file called tests/setup.rs to handle the API authentication and bucket initialization logic. He then tried to call those setup functions from his actual test files. The problem? He was staring at a wall of compiler errors and a very confusing cargo test output that insisted setup.rs was its own test suite, but it contained no tests to run.
This is a classic Rust "gotcha." Because of how Cargo handles the tests/ directory, it doesn't work like a standard module tree. If you aren't careful, you'll end up with a project that feels like it's fighting you every time you try to share a single helper function.
The Boundary of the Public API
In Rust, unit tests live inside your src/ files, often in a nested mod tests. Those tests have "insider access" to your private functions. Integration tests, however, live in the top-level tests/ directory. When you put a file here, Cargo compiles it as a separate crate.
This is intentional. By forcing the integration tests into their own crates, Rust ensures you are testing your library exactly how a user would: by calling your public API. If you find yourself wishing you could test a private function in an integration test, you're actually discovering that the function probably belongs in a separate internal module that should be public to your own crate, but not to the world.
For example, if you have a library called cloud_vault, your integration test file tests/upload_tests.rs will start with use cloud_vault::VaultClient;. You cannot use use crate::... because, as far as the compiler is concerned, the test file is a completely different program than the library it's testing.
Avoiding the Compilation Trap with Shared Modules
Now, back to my teammate's struggle. If every .rs file directly under tests/ is treated as a separate crate, then tests/setup.rs becomes its own test executable. Cargo sees it, tries to run it, finds no #[test] functions, and warns you—all while your other tests fail to import it because you can't easily import from one test crate into another.
The fix is simple but specific: move shared logic into a sub-directory. Cargo only treats files in the root of tests/ as separate crates. Anything in a sub-folder is ignored by the test runner and can be treated as a normal module.
The standard pattern looks like this:
project_root/
├── src/
│ └── lib.rs
└── tests/
├── common/
│ └── mod.rs <-- Shared helper functions go here
├── upload_tests.rs
└── download_tests.rs
In tests/common/mod.rs, you define your setup logic. Then, in tests/upload_tests.rs, you can simply add mod common;. Now, common is a module within the upload_tests crate, not a standalone crate itself. I've found that this is the only way to keep a large integration suite from becoming a repetitive mess of boilerplate code.
📋 Practical Task
Structuring a Mock API Client Integration Suite
You are building a library that interacts with a weather API. You need to set up an integration test suite that shares a common "Mock Server" setup to avoid duplicating the server start/stop logic across multiple test files.
Requirements:
- Create a
tests/directory in your project root. - Create a sub-directory
tests/common/with amod.rsfile. Insidemod.rs, define a public functionsetup_mock_server()that returns aString(representing the server URL). - Create a test file
tests/weather_tests.rs. - In
weather_tests.rs, declare thecommonmodule and write a test functiontest_get_temperature()that callscommon::setup_mock_server()and asserts that the returned URL is not empty.
Goal: Run cargo test and verify that only weather_tests is executed as a test crate, and that it successfully utilizes the logic inside the common module without Cargo attempting to run common as a standalone test.
There are no comments for now.