Мок-экземпляр внутри реализации serde

Я пытаюсь реализовать пользовательскую функцию/метод десериализации, который использует некоторые внешние функции. Функция создает экземпляр и использует его методы. Он работает нормально, но я не могу понять, как издеваться над сервисом в тестах.

Более общий вопрос: как предоставить состояние функции/методу десериализации?

Код ниже иллюстрирует то, что я имею в виду.

MagickBook — это внешняя служба, которая хранит состояние и содержит некоторую важную логику в методе MagickBook::find.

Scroll — это десериализуемая структура данных, которую следует десериализовать, используя логику из MagicBook.

Я хотел бы иметь способ предоставить конкретный экземпляр MagicBook извне в момент десериализации. Например в тестах.

Игровая площадка Rust

use serde::de::{Deserialize, Deserializer}; // 1.0.82
use serde_derive::Deserialize; // 1.0.82
use serde_json; // 1.0.33

struct MagickBook;

// Some service which I want to mock in the test
impl MagickBook {
    fn new() -> Self {
        Self {}
    }

    fn find(&self, _kind: &str) -> isize {
        let effect = 42;
        // Here we do some logic depending on input parameter
        // -- snip --
        return effect;
    }
}

#[derive(Deserialize, PartialEq, Debug)]
struct Scroll {
    #[serde(rename = "kind")]
    #[serde(deserialize_with = "deserialize_effect")]
    effect: isize,
}

fn deserialize_effect<'de, D>(deserializer: D) -> Result<isize, D::Error>
where
    D: Deserializer<'de>,
{
    let book = MagickBook::new();
    Ok(book.find(&String::deserialize(deserializer)?))
}

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

    #[test]
    fn main() {
        let scroll: Scroll = serde_json::from_str("{\"kind\":\"wind\"}").unwrap();
        assert_eq!(scroll, Scroll { effect: 42 });
    }
}

person German Lashevich    schedule 05.01.2019    source источник


Ответы (1)


Я бы порекомендовал получить доступ к макету через внутренне изменяемый локальный экземпляр потока.


use serde::{Deserialize, Deserializer};
use std::cell::RefCell;

struct MagickBook {
    value: isize,
}

thread_local! {
    static MAGICK_BOOK: RefCell<MagickBook> = RefCell::new(MagickBook::new());
}

impl MagickBook {
    fn new() -> Self {
        MagickBook { value: 0 }
    }

    fn find(&self, _kind: &str) -> isize {
        let effect = self.value;
        // -- snip --
        effect
    }
}

#[derive(Deserialize, PartialEq, Debug)]
struct Scroll {
    #[serde(rename = "kind", deserialize_with = "deserialize_effect")]
    effect: isize,
}

fn deserialize_effect<'de, D>(deserializer: D) -> Result<isize, D::Error>
where
    D: Deserializer<'de>,
{
    let kind = String::deserialize(deserializer)?;
    Ok(MAGICK_BOOK.with(|book| book.borrow().find(&kind)))
}

#[test]
fn test_deserialize() {
    MAGICK_BOOK.with(|book| book.borrow_mut().value = 42);
    let scroll: Scroll = serde_json::from_str(r#"{"kind":"wind"}"#).unwrap();
    assert_eq!(scroll, Scroll { effect: 42 });
}
person dtolnay    schedule 05.01.2019