views:

119

answers:

2

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!

A: 

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

Mark Seemann
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?
L2Type
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...
Mark Seemann
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?
Mark Seemann
What is 'Blue Peter'?
Mark Seemann
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.
L2Type
+2  A: 

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.

Darin Dimitrov
+1 This very clearly expresses many of the things I could only hint at in my own answer and comments. Excellent answer.
Mark Seemann
That makes a lot of sense, thank you
L2Type