How can I create a mock Web::Syndication::SyndicationFeed with the Rust for Windows API for unit testing

47 views Asked by At

I have started out with the Rust for Windows RSS reader example documented on Microsoft's website. It works as expected but is rather simplistic. I want to expand on this example, turn it into something useful, and create a sample project that includes unit testing.

My application code does work as expected. Since network issues are common errors, I structured main so that the external call to retrieve the feed is in one function and the code that parses the feed (if one is returned) is in another.

fn check_syndication(client: &SyndicationClient) -> Result<SyndicationFeed> {
    let uri = Uri::CreateUri(h!("https://rpilocator.com/feed"))?;
    client.RetrieveFeedAsync(&uri)?.get()
}

fn parse_syndication(syndication_feed: SyndicationFeed) -> Vec<String> {
    let feed_items: IVector<SyndicationItem> = syndication_feed.Items().unwrap();

    feed_items
        .into_iter()
        .map(|item| item.Title().unwrap().Text().unwrap().to_string())
        .collect::<Vec<String>>()
}

The idea was to create a mock SyndicationFeed object and then pass that into my parse_syndication() function like so:

#[test]
fn verify_parse_syndication() {
   let mock_uri = Uri::CreateUri(h!("https://mock_url.com")).unwrap();
   let mock_feed = SyndicationFeed::CreateSyndicationFeed(
      &HSTRING::from("Mock Title"),
      &HSTRING::from("Mock Subtitle"),
      &mock_uri,
   ).unwrap();
   
   let output = parse_syndication(mock_feed);
   assert_eq!(output.len(), 1);
}

While this test does compile and create a empty SyndicationFeed, I haven't been able to figure out how to populate it so that SyndicationFeed.Items contains a mocked up list of items.

IVector<SyndicationItem>

The full source code on GitHub

The SyndicationFeed documentation doesn't indicate that there is any type of add() or insert() functionality. The CreateSyndicationFeed() function doesn't seem to provide a way to initialize the SyndicationFeed with data. I haven't found and other example of creating one in my search of GitHub, SO, or Google in general. I also searched for factory patterns and the creation of other similar types of object in the API.

I did try a different approach where I created a mock RSS feed in a xml file. Then I created a URI object that pointed to the local resource and provided it as an input to the RetrieveFeedAsync() call. Unfortunately I couldn't get that to work either. There were multiple issues with mixing forward slashes from the URI syntax and backslashes from the pathbuf. Even a hard coded string with the local resource would fail to open the file. I eventually abandoned this approach.

1

There are 1 answers

0
Aven Arlington On

As IInspectable suggested, I was able to generate a mock XmlDocument with a String and call SyndicationFeed::LoadFromXml to get the expected SyndicationFeed output that I can test against.

mod tests {
    use super::*;
    use windows::Data::Xml::Dom::XmlLoadSettings;
    pub struct XmlDoc {
        mock_xml: XmlDocument,
    }

    impl Default for XmlDoc {
        fn default() -> Self {
            let xml = String::from(
                "\
                <?xml version=\"1.0\" encoding=\"UTF-8\" ?><rss version=\"2.0\">\
                    <channel>\
                        <title>Mock Channel Title</title>\
                        <link>https://mockchannellink.com</link>\
                        <description>Mock Channel Description</description>\
                        <lastbuilddate>Fri, 08 Dec 2023 01:00:00 GMT</lastbuilddate>\
                        <item>\
                            <title>Mock Item Title</title>\
                            <description>Mock Item Description.</description>\
                            <link>https://mockitemlink.com</link>\
                            <guid ispermalink=\"false\">846c1690-b4ad-479a-95e3-2ca5ed4dfdbd</guid>\
                            <pubdate>Fri, 08 Dec 2023 01:00:00 GMT</pubdate>\
                        </item>\
                    </channel>\
                </rss>\
                ",
            );

            let xml_opts = XmlLoadSettings::new().expect("Opts creation failed");
            xml_opts
                .SetValidateOnParse(true)
                .expect("Set validate option failed");

            let mock_xml: XmlDocument = XmlDocument::new().expect("New XmlDoc::new failed");
            match mock_xml.LoadXmlWithSettings(&HSTRING::from(xml), &xml_opts) {
                Ok(doc) => doc,
                Err(e) => {
                    println!("LoadXml failed: {}", e.code());
                }
            };
            Self { mock_xml }
        }
    }

    #[test]
    fn verify_parse_syndication() {
        let xml_doc = XmlDoc::default().mock_xml;

        let mock_feed = SyndicationFeed::new().expect("New SyndicationFeed failed");
        mock_feed
            .LoadFromXml(&xml_doc)
            .expect("Load from xml failed");
        let output = parse_syndication(mock_feed);
        assert_eq!(output.len(), 1);
    }
}