How do you fill large structs with dummy data for testing?

I'm writing an app that works with egg-mode which has a large pub Tweet struct: https://tonberry.quietmisdreavus.net/doc/egg_mode/tweet/struct.Tweet.html

For offline unit testing I need to create tweets.

Attempt 1: just use a real tweet in a JSON file and deserialize it:

let json = {
    let mut file = File::open("src/twitter_status.json").unwrap();
    let mut ret = String::new();
    file.read_to_string(&mut ret).unwrap();
    ret
};
Tweet::from_str(json)

Does not work because egg_mode::common::from_json::FromJson is private and I cannot access that as egg-mode library consumer. I should probably file an issue against egg-mode to make that accessible?

The problem is that Tweet has so many fields that it is very tedious to construct by hand. A more general approach to testing with structs in Rust would be a library that fills any struct with dummy data. I'm thinking of an API like this:

let tweet = fill_struct_with_crap<Tweet>();

Does such a library exist? What else could I do to get test data into a Tweet without handcrafting 200 lines of struct filling code?

In the meantime I'm hard-coding this into my tests, welp:

fn get_twitter_status() -> Tweet {
    Tweet {
        coordinates: None,
        created_at: Utc::now(),
        current_user_retweet: None,
        display_text_range: None,
        entities: TweetEntities {
            hashtags: Vec::new(),
            symbols: Vec::new(),
            urls: Vec::new(),
            user_mentions: Vec::new(),
            media: None,
        },
        extended_entities: None,
        favorite_count: 0,
        favorited: None,
        id: 123456,
        in_reply_to_user_id: None,
        in_reply_to_screen_name: None,
        in_reply_to_status_id: None,
        lang: "".to_string(),
        place: None,
        possibly_sensitive: None,
        quoted_status_id: None,
        quoted_status: None,
        retweet_count: 0,
        retweeted: None,
        retweeted_status: None,
        source: TweetSource {
            name: "".to_string(),
            url: "".to_string(),
        },
        text: "".to_string(),
        truncated: false,
        user: None,
        withheld_copyright: false,
        withheld_in_countries: None,
        withheld_scope: None,
    }
}

Once you have this "template" struct defined, you can use struct update syntax to create new instances with just a few fields set to some values you want to base the tests on.

Yep, that is what I'm doing now:

let mut tweet = get_twitter_status();
tweet.text = "My test text".to_string();

I meant something like:

// custom value for `field`, all else copied from `template`
let tweet = Tweet { field: value, .. get_twitter_status() };

But it's not much different from manually overwriting the values.

2 Likes

I am usually trying to provide a default implementation, which tends to be useful anyways and can be re-used for test vector initialization + random modification of selected values.

In general: Pure randomly filled structures tend to be invalid / no use case either. Handling that would require some additional logic, making the test itself pointless because the filter will essentially have all the test logic which kills the atomic / module test concept.