How to spy on your Rust code
In the following code, how can you test that Player
is making the correct API
calls?
struct Player<'a> {
api: &'a Api,
}
impl<'a> Player<'a> {
pub fn new(api: &'a Api) -> Player<'a> {
Player { api }
}
pub fn play(&self, song: &str) {
self.api.sing(song);
}
pub fn stop(&self) {
self.api.shush();
}
}
The answer? Make it generic!
struct Player<'a, T> {
api: &'a T,
}
impl<'a, T> Player<'a, T>
where
T: PlayerApi,
{
pub fn new(api: &'a T) -> Player<'a, T> {
Player { api }
}
// ...
}
trait PlayerApi {
// Default trait implementation uses `Api`
fn sing(&self, url: &str);
fn shush(&self);
}
impl PlayerApi for Api {
fn sing(&self, url: &str) {
Api::sing(self, url)
}
fn shush(&self) {
Api::shush(self)
}
}
Then you can easily spy on it:
#[cfg(test)]
module Test {
struct ApiSpy {
pub invocations: Vec<String>,
api: Api,
}
impl ApiSpy {
pub fn new() -> ApiSpy {
ApiSpy { api: Api::New() }
}
}
impl PlayerApi for ApiSpy {
fn sing(&self, url: &str) {
self.invocations.push('play');
self.api.sing(url)
}
}
#[test]
fn test_play() {
let api = ApiMock::new();
let player = Player::new(&api);
player.play("my_url");
assert_eq!(api.invocations[0], "play");
}
}
That’s it!
How can I assert that I passed the correct arguments?
You can store the arguments in the ApiSpy
. For example, here’s how I mocked
the mpv api and used it in a test.
I don’t want to define a trait for my API just for tests!
Defining a trait for your external APIs is good for reasons beyond testing. But if you still still don’t want to make your API generic, then you could use conditional compilation instead..
What if I don’t want to execute API code in tests?
If you want to mock instead of spy, you have a couple options:
- If your API’s return types are easy to mock, then just return mock responses instead of proxying.
- You could also use a mocking library. Just a caveat: I had trouble with many of the crates, but maybe you’ll have better luck.
- Finally, you could try conditional compilation as mentioned above. This lets let you get around type checking.