I have troubles figuring out unit tests for the methods of the target struct.
I have a method random_number
that returns a random value based on the att
Thanks to @Shepmaster I came to this workaround. I have added the actual Rng
to have more context.
use rand::{thread_rng, Rng}; // 0.6.5
struct RngTest(Vec<u64>);
impl RngTest {
fn random_number(&self) -> u64 {
let random_value = thread_rng().choose(&self.0);
*random_value.unwrap()
}
fn plus_one(&self) -> u64 {
self.random_number() + 1
}
}
#[test]
fn plus_one_works() {
let rng = RngTest(vec![1]);
assert_eq!(rng.plus_one(), 2);
}
I can set an appropriate value in the object and don't need to use traits. There is a downside though - this forces me to have a special instance of my type for this specific test which I would like to avoid because my actual type has a lot of fields and I wanted to define its creation once for all the tests using speculate
.
How to mock specific methods but not all of them in Rust?
As you have already learned, you cannot replace methods on a type. The only thing you can do is move the methods to a trait and then provide production and test-specific implementations of that trait. How you structure the trait determines the granularity of what you are able to test.
Depending on your use case, you might be able to use a default implementation:
trait SomeRng {
fn random_number(&self) -> u64;
fn plus_one(&self) -> u64 {
self.random_number() + 1
}
}
struct RngTest(u64);
impl SomeRng for RngTest {
fn random_number(&self) -> u64 {
self.0
}
}
#[test]
fn plus_one_works() {
let rng = RngTest(41);
assert_eq!(rng.plus_one(), 42);
}
Here, random_number
is a required method, but plus_one
has a default implementation. Implementing random_number
gives you plus_one
by default. You could also choose to implement plus_one
if you could do it more efficiently.
The real rand crate uses two traits:
Rng
pub trait Rng: RngCore { /* ... */ }
An automatically-implemented extension trait on
RngCore
providing high-level generic methods for sampling values and other convenience methods.
RngCore
pub trait RngCore { /* ... */ }
The core of a random number generator.
This splits the core interesting parts of the implementation from the helper methods. You can then control the core and test the helpers:
trait SomeRngCore {
fn random_number(&self) -> u64;
}
trait SomeRng: SomeRngCore {
fn plus_one(&self) -> u64 {
self.random_number() + 1
}
}
impl<R: SomeRngCore> SomeRng for R {}
struct RngTest(u64);
impl SomeRngCore for RngTest {
fn random_number(&self) -> u64 {
self.0
}
}
#[test]
fn plus_one_works() {
let rng = RngTest(41);
assert_eq!(rng.plus_one(), 42);
}