I have a struct which implements Deserialize
and uses the serde(deserialize_with)
on a field:
#[derive(Debug, Deserialize)]
struct Record {
name: String,
#[serde(deserialize_with = "deserialize_numeric_bool")]
is_active: bool,
}
The implementation of deserialize_numeric_bool
deserializes a string "0" or "1" to the corresponding boolean value:
pub fn deserialize_numeric_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
where D: Deserializer<'de>
{
struct NumericBoolVisitor;
impl<'de> Visitor<'de> for NumericBoolVisitor {
type Value = bool;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("either 0 or 1")
}
fn visit_u64<E>(self, value: u64) -> Result<bool, E>
where E: DeserializeError
{
match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(E::custom(format!("invalid bool: {}", value))),
}
}
}
deserializer.deserialize_u64(NumericBoolVisitor)
}
(I appreciate comments about code improvements)
I'd like to write unit tests for deserialization functions like deserialize_numeric_bool
. Of course, my friendly search box revealed the serde_test
crate and a documentation page about unit-testing.
But these resources couldn't help me in my case, as the crate tests a structure directly implementing Deserialize
.
One idea I had was to create a newtype which only contains the output of my deserialize functions and test it with it. But this looks like a unnecessary indirection to me.
#[derive(Deserialize)]
NumericBool {
#[serde(deserialize_with = "deserialize_numeric_bool")]
value: bool
};
How do I write idiomatic tests for it?
My current solution uses only structures already provided by serde
.
In my use case, I only wanted to test that a given string will deserialize successfully into a bool or has a certain error. The serde::de::value
provides simple deserializers for fundamental data types, for example U64Deserializer
which holds a u64
. It also has an Error
struct which provides a minimal representation for the Error traits – ready to be used for mocking errors.
My tests look currently like that: I mock the input with a deserializer and pass it to my function under test. I like that I don't need an indirection there and that I have no additional dependencies. It is not as nice as the assert_tokens*
provided serde_test
, as it needs the error struct and feels less polished. But for my case, where only a single value is deserialized, it fulfills my needs.
use serde::de::IntoDeserializer;
use serde::de::value::{U64Deserializer, StrDeserializer, Error as ValueError};
#[test]
fn test_numeric_true() {
let deserializer: U64Deserializer<ValueError> = 1u64.into_deserializer();
assert_eq!(numeric_bool(deserializer), Ok(true));
}
#[test]
fn test_numeric_false() {
let deserializer: U64Deserializer<ValueError> = 0u64.into_deserializer();
assert_eq!(numeric_bool(deserializer), Ok(false));
}
#[test]
fn test_numeric_invalid_number() {
let deserializer: U64Deserializer<ValueError> = 2u64.into_deserializer();
let error = numeric_bool(deserializer).unwrap_err();
assert_eq!(error.description(), "invalid bool: 2");
}
#[test]
fn test_numeric_empty() {
let deserializer: StrDeserializer<ValueError> = "".into_deserializer();
let error = numeric_bool(deserializer).unwrap_err();
assert_eq!(error.description(), "invalid type: string \"\", expected either 0 or 1");
}
I hope that it helps other folks too or inspire other people to find a more polished version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With