Testing in Rust: Frameworks and Tools

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Testing in Rust: Frameworks and Tools



Rust is a systems programming language that places a huge emphasis on code reliability and safety. The built-in testing system allows developers to write unit tests, integration tests, and documentation tests without installing additional libraries. However, for complex scenarios — parameterization, random data generation, or mocking — the Rust community has created powerful frameworks. In this article, we will break down the key testing tools in Rust.



Built-in test Module



Rust comes with a built-in test module, which is activated via the #[test] attribute. The cargo test command is used to run tests. Example of a simple unit test:



// src/lib.rspub fn add(a: i32, b: i32) -> i32 {    a + b}

#[cfg(test)]mod tests { use super::*;

#[test] fn test_add() { assert_eq!(add(2, 2), 4); }

#[test] fn test_add_negative() { assert_eq!(add(-1, 1), 0); }}


Key features of the built-in module:

  • Attributes: #[test], #[ignore], #[should_panic].
  • Assertion macros: assert!, assert_eq!, assert_ne!.
  • Integration tests: placed in the tests/ directory and test the public API.
  • Documentation tests: code examples in doc-comments, verified via cargo test.


For most projects, this is sufficient. But when parameterization, random data generation, or mocking is required, specialized frameworks come to the rescue.



rstest Framework — Parameterized Tests



The rstest library provides a powerful mechanism for test parameterization, similar to pytest in Python. It allows you to specify multiple sets of input data and expected results without code duplication.



Installation:

[dev-dependencies]rstest = "0.22"


Example of a parameterized test:



use rstest::rstest;

#[rstest]#[case(1, 2, 3)]#[case(0, 0, 0)]#[case(-1, 1, 0)]fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) { assert_eq!(add(a, b), expected);}


Capabilities of rstest:

  • Parameterization via #[case].
  • Random data generation via #[random].
  • Contextual fixtures via #[fixture].
  • Support for asynchronous tests.


This framework is especially useful when testing mathematical functions, parsers, or algorithms where many edge cases need to be checked.



Property-based Testing with proptest



Property-based testing is a method where you describe invariants (properties) that must hold for all possible input data. The proptest library generates random input data and checks these properties.



Installation:

[dev-dependencies]proptest = "1.5"


Example: verifying that sorting preserves length and all elements:



use proptest::prelude::*;

proptest! { #[test] fn test_sort_keeps_elements(mut vec: Vec<i32>) { let original_len = vec.len(); let mut sorted = vec.clone(); sorted.sort(); assert_eq!(sorted.len(), original_len); for item in &vec { assert!(sorted.contains(item)); } }}


Advantages of proptest:

  • Automatic generation of thousands of random scenarios.
  • Shrinking — when a test fails, the library tries to find the minimal failing input.

Recommendations