5

I'm trying to get into the unit testing and TDD way of doing things but I've come across a problem I'm not sure what to do with.

I have a collection that saves itself to disk using XDocument and XmlWriter. I know you shouldn't write the file to disk then check it so I had the XmlWriter output to a memory stream then I checked the contents of the memory stream. The function looks like this:

   public void Save()
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(m_StreamProvider.SaveFileStream(m_FilenameProvider.Filename)))
        {
            XDocument xDoc = new XDocument(new XElement("BookmarkCollection",
                    Items.Select(bookmark => new XElement("Bookmark",
                        new XElement("Name", bookmark.Name),
                        new XElement("Link", bookmark.Link),
                        new XElement("Remarks", bookmark.Remarks),
                        new XElement("DateAdded", bookmark.DateAdded),
                        new XElement("DateLastAccessed", bookmark.DateLastAccessed))
                        )
                        ));

            xDoc.Save(xmlWriter);
        }

    }

And the unit test is

[Test]
    public void Save_OneItemCollection_XmlCreatedCorrectly()
    {
        //Arrange
        MemoryStreamProvider streamProvider = new MemoryStreamProvider();
        IBookmarkCollection collection = XBookmarkTestHelpers.GetXBookmarkCollection(streamProvider);

        IBookmark bookmarkToAdd = XBookmarkTestHelpers.GetIBookmark("myLink");
        collection.Add(bookmarkToAdd);

        //Act
        collection.Save();

        //Assert
        streamProvider.WriteStrean.Position = 0;
        String generatedXml = Encoding.Default.GetString(streamProvider.WriteStrean.GetBuffer());

        Assert.IsTrue(String.Equals(generatedXml, m_ExpectedOneItemString), "XML does not match");
    }

The assert here fails too fragile (I know I could use String.Compare() but it would have similar issues.), Am I testing the right thing? Am I mocking the wrong thing?

All input greatly appreciated!

2 Answers 2

5

The first thing that feels wrong about the Save function is that it actually has two responsibilities: it selects a storage and it serializes an object graph to this storage. I would start by isolating responsibilities:

public void Save(XmlWriter xmlWriter)
{
    XDocument xDoc = new XDocument(new XElement("BookmarkCollection",
        Items.Select(bookmark => new XElement("Bookmark",
            new XElement("Name", bookmark.Name),
            new XElement("Link", bookmark.Link),
            new XElement("Remarks", bookmark.Remarks),
            new XElement("DateAdded", bookmark.DateAdded),
            new XElement("DateLastAccessed", bookmark.DateLastAccessed))
        )
    ));
    xDoc.Save(xmlWriter);
}

public void Save()
{
    using (XmlWriter xmlWriter = XmlWriter.Create(m_StreamProvider.SaveFileStream(m_FilenameProvider.Filename)))
    {
        Save(xmlWriter);
    }
}

As far as the unit test is concerned, you could define an XSD schema and then validate the result XML against this schema and also test that it contains the values you are looking for.

Sign up to request clarification or add additional context in comments.

1 Comment

+1 This very clearly expresses many of the things I could only hint at in my own answer and comments. Excellent answer.
1

In such cases, I use XNode.DeepEquals to compare XML instances with each other because it compares structure, not bytes.

4 Comments

I was thinking about loading the string into an XDocument then comparing with a 'Blue Peter' style one I made earlier, but is that going to far?
You should only make assertions about the changes that you care about. Sometimes that would be the entire XML document, and sometimes it would be that a very specific elemet/attribute was added. In the latter case, I would query the resulting document with XLINQ to examine the node in question. I'm not, however, sure that this was exactly what you asked about...
OTOH, I definitely think you are on the right track when it comes to using an XmlWriter instead of hitting the disk, so I definitely appreciate the added flexibility. However, it seems to me that your API still has a 'file system smell' over it. Why not just operate directly on an XmlWriter and forget about the file system?
I haven't phrased it very but I was trying to see if I was doing right. I think the querying it with XLINQ might be the way to go.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.