Search code examples
testingrustserde

Testing serialize/deserialize functions for serde "with" attribute


Serde derive macros come with the ability to control how a field is serialized/deserialized through the #[serde(with = "module")] field attribute. The "module" should have serialize and deserialize functions with the right arguments and return types.

An example that unfortunately got a bit too contrived:

use serde::{Deserialize, Serialize};

#[derive(Debug, Default, PartialEq, Eq)]
pub struct StringPair(String, String);

mod stringpair_serde {
    pub fn serialize<S>(sp: &super::StringPair, ser: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        ser.serialize_str(format!("{}:{}", sp.0, sp.1).as_str())
    }

    pub fn deserialize<'de, D>(d: D) -> Result<super::StringPair, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        d.deserialize_str(Visitor)
    }

    struct Visitor;

    impl<'de> serde::de::Visitor<'de> for Visitor {
        type Value = super::StringPair;

        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "a pair of strings separated by colon (:)")
        }

        fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
        where
            E: serde::de::Error,
        {
            Ok(s.split_once(":")
                .map(|tup| super::StringPair(tup.0.to_string(), tup.1.to_string()))
                .unwrap_or(Default::default()))
        }
    }
}

#[derive(Serialize, Deserialize)]
struct UsesStringPair {
    // Other fields ...

    #[serde(with = "stringpair_serde")]
    pub stringpair: StringPair,
}

fn main() {
    let usp = UsesStringPair {
        stringpair: StringPair("foo".to_string(), "bar".to_string()),
    };

    assert_eq!(
        serde_json::json!(&usp).to_string(),
        r#"{"stringpair":"foo:bar"}"#
    );

    let usp: UsesStringPair = serde_json::from_str(r#"{"stringpair":"baz:qux"}"#).unwrap();

    assert_eq!(
        usp.stringpair,
        StringPair("baz".to_string(), "qux".to_string())
    )
}

Testing derived serialization for UsesStringPair is trivial with simple assertions. But I have looked at serde_test example as that makes sense to me too.

However, I want to be able to independently test the stringpair_serde::{serialize, deserialize} functions (e.g. if my crate provides just mycrate::StringPair and mycrate::stringpair_serde, and UsesStringPair is for the crate users to implement).

One way I've looked into is creating a serde_json::Serializer (using new, requires a io::Write implementation, which I couldn't figure out how to create and use trivially, but that's a separate question) and calling serialize with the created Serializer, then making assertions on the result as before. However, that does not test any/all implementations of serde::Serializer, just the one provided in serde_json.

I'm wondering if there's a method like in the serde_test example that works for ser/deser functions provided by a module.


Solution

  • There is no method within serde_test that can test these functions directly. While serde_test uses its own Serializer and Deserializer internally, it does not expose these types, so you can't use them directly in your testing.

    However, you can accomplish this with the serde_assert crate (disclaimer: I wrote serde_assert). serde_assert directly exposes its Serializer and Deserializer specifically for testing, and works well for your use case:

    use serde_assert::{Deserializer, Serializer, Token, Tokens};
    
    #[test]
    fn serialize() {
        let serializer = Serializer::builder().build();
    
        assert_eq!(
            stringpair_serde::serialize(
                &StringPair("foo".to_string(), "bar".to_string()),
                &serializer
            ),
            Ok(Tokens(vec![Token::Str("foo:bar".to_string())])),
        );
    }
    
    #[test]
    fn deserialize() {
        let mut deserializer = Deserializer::builder()
            .tokens(Tokens(vec![Token::Str("foo:bar".to_string())]))
            .build();
    
        assert_eq!(
            stringpair_serde::deserialize(&mut deserializer),
            Ok(StringPair("foo".to_string(), "bar".to_string()))
        );
    }