10

I'm currently trying to learn proper unit-test. So now I'm trying to write unit-tests for a class that should map data from an XML-File to the proper objects. Of course all functionality of the class is dependent on the existence of the corresponding XML-file. The XML-file is loaded in the constructor of the class.

I'm using C# with NUnit. So far I've got two tests:

[Test]
public void ShouldAllowInstanceToBeCreatedWhenXMLFileIsPresent()
{
    if (File.Exists(SettingsReader.XML_SETTINGS_PATH))
    {
        SettingsReader settingsReader = new SettingsReader();
        Assert.AreNotEqual(null, settingsReader);
    }
}

[Test]
[ExpectedException("Telekanzlei.Clientmanager.XMLDataLayer.XMLFileNotFoundException")]
public void ShouldThrowExceptionWhenXMLFileIsNotPresent()
{
    if (!File.Exists(SettingsReader.XML_SETTINGS_PATH))
    {
        SettingsReader settingsReader = new SettingsReader();
    }
        else
            throw new XMLFileNotFoundException();
    }

I'm not sure if checking the existence of the file in the test is a proper way to go, so any suggestions on those test are welcome too. But my question is, how to proceed with the following tests. Obviously all following tests are going to fail, if the XML-file is not present.

So do I assume that the XML-file is present, while keeping in mind, that a failing test could just mean that it's not? That wouldn't seem right to me.

Is there a general pattern, to handle a problem like this?

Thx for any help

edit: rewrote the second test, as it was failing if the file was actually present...

edit2: May it is helping to tell you, what the SettingsReader actually does. So far it looks like this:

public class SettingsReader
{
    public static readonly string XML_SETTINGS_PATH = "C:\\Telekanzlei\\Clientmanager_2.0\\Settings.xml";

    public XElement RootXElement { get; private set; }

    public SettingsReader()
    {
        if (!File.Exists(XML_SETTINGS_PATH))
            throw new XMLFileNotFoundException();
        using (var fs = File.OpenRead(XML_SETTINGS_PATH))
        {
            RootXElement = XElement.Load(fs);
        }
    }


}

I'm not sure, but I guess a StreamReader wouldn't be the way to go here, would it?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Tobi
  • 5,499
  • 3
  • 31
  • 47

4 Answers4

14

The problem is not with your unit tests but with the design of the class. I'd suggest refactoring the class so it doesn't open the file but instead operates on a stream. Then your unit tests could simply replace a file stream for a memory stream - simples! :)

public class SettingsReader()
{
    public SettingsReader(System.IO.StreamReader reader)
    {
        // read contents of stream...
    }
}

// In production code:
new SettingsReader(new StreamReader(File.Open("settings.xml")));

// In unit test:
new SettingsReader(new StringReader("<settings>dummy settings</settings>"));

Remember, opening a file and parsing settings data are two very different concerns.

MattDavey
  • 8,897
  • 3
  • 31
  • 54
  • I just realized that the XElement.Load-method is taking a stream as parameter. I'm not that familiar with .NET yet. But your suggestion seems like a good approach. So I'm going to split that in lets say a SettingsStreamProvider-class and a SettingsParser-class and use dependency injection for the parser? – Tobi Mar 02 '12 at 21:54
  • What value would a setting stream provider class have over just a plain stream? I think that would be a pointless abstraction - KISS :) – MattDavey Mar 02 '12 at 22:02
  • Point taken^^ The thing is thoughm I'm using the SettingsReader on multiple occasions in my project, but always with the same XML-file. I wouldn't like it very much, to create a new stream "manually" everytime I use it. That's why I wanted to handle the stream-opening in the SettingsReader-class in the first place... – Tobi Mar 02 '12 at 22:14
  • But I guess I'm taking your approach anyway. Would give me the possibility to test reactions to corrupted xml-files - by using your StringReader-suggestion - without actually having to provide a corrupted file. – Tobi Mar 02 '12 at 22:25
  • Hmm in that case I would think about having some kind of settings manager class.. I don't usually advocate singletons but this would be a good candidate. Then you have a single point of contact whenever you need to load/save settings. And the manager would be responsible for opening the file and passing a stream to the reader... – MattDavey Mar 05 '12 at 13:05
5

If you must I suggest you use SetUp method to copy or verify that the file exist. I suggest making sure the file is present by adding it to the test project and marking it as "copy always" once you get that working there is no need to re-check it.
If you have a lot of tests that require external files perhaps you should use MsTest - it has an attribute called DeploymentItem that makes sure that the file is copied to the same location as the test.

Jay
  • 56,361
  • 10
  • 99
  • 123
Dror Helper
  • 30,292
  • 15
  • 80
  • 129
3

Consider rewriting code so dependencies can be passed in or somehow else stubbed for the code you want to unit-test.

I.e. pass something like "IMySettingsFileProvider" instance to SettingsReader constructor where IMySettingsFileProvider.SettingsXml returns some setting stream. This way you can mock IMySettingsFileProvider interface for the test instead of requiring file to be present on disk.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • 1
    Wouldn't that be a bit of an "overkill"? The XML-file is only used by this single class. Creating an Interface + class only to load the file? – Tobi Mar 02 '12 at 21:31
  • 1
    Agreed. I don't understand why people add so much complexity with mocking just because it's the "pure" way to do it. You are better off leveraging the code that created the file to create it / copy it to a temp location, use it, then delete it. – tsells Mar 02 '12 at 21:47
  • Yes it could be. That why I tried to write "consider..." - if it works for your case than do that, if not - do something else (which is already was suggested as an earlier answer). – Alexei Levenkov Mar 02 '12 at 21:53
1

One option is to put this at the top of the test fixture. Then the tests will only be valid when the file exists.

[SetUp]
public void Setup()
{
    Assume.That(File.Exists(SettingsReader.XML_SETTINGS_PATH));
}
Scroog1
  • 3,539
  • 21
  • 26