I am trying to write unit tests for a new project I have created and I've run into an issue where I can't work out how a class that I am intending to write is actually testable. Below I've simplified the class that I am trying to write to give you an idea of what I am trying to achieve.
So I have an XML parser that will simply access an XML file from a given URL, extract the data that I need and return it as an object. So my code will look something like this (Validation and population not completed yet but you get the idea):
public UserDetails ParseUserDetails(string request, string username, string password)
{
var response = new XmlDocument();
response.Load(string.Format(request + "?user={0}&password={1}", username, password));
// Validation checks
return new UserDetails { // Populate object with XML nodes };
}
Currently my class isn't testable. I can't mock the load to throw a WebException to see how my class handles errors and until I pass through a valid URL it will always throw an exception when I run tests against this class. I also cannot test the data coming back from the class since I can't mock the XML document since it's loaded from another URL.
I could split this out into a mockable object that retrieves the XML from the URL and name it something like IXmlDocumentLoader but later I run into the same problem where I have a class like this:
public class XmlDocumentLoader : IXmlDocumentLoader
{
public XmlDocument LoadXmlDocument(string request, string username, string password)
{
var response = new XmlDocument();
response.Load(string.Format(request + "?user={0}&password={1}", username, password));
return response;
}
}
This would make the ParseUserDetails method more testable but now the class XmlDocumentLoader is not testable. So have I just moved the problem elsewhere? My question is really do all classes have to be testable or am I misunderstanding unit testing?
Do I need to write a test class for every class I need to test? No. It is a convention to start with one test class per class under test, but it is not necessary. Test classes only provide a way to organize tests, nothing more.
(Not) Testing Get/Set Methods Every behavior should be covered by a unit test, but every method doesn't need its own unit test. Many developers don't test get and set methods, because a method that does nothing but get or set an attribute value is so simple that it is considered immune to failure.
Unit Tests Should Only Test Public Methods The short answer is that you shouldn't test private methods directly, but only their effects on the public methods that call them. Unit tests are clients of the object under test, much like the other classes in the code that are dependent on the object.
The answer to the more general question is yes, you should unit test everything you can. Doing so creates a legacy for later so changes down the road can be done with peace of mind. It ensures that your code works as expected.
This for sure is an "opinion" question, and as so, probably will be closed.
But I'll give you a suggestion.
Split everything. Use the principle of "an object should do only one thing". Donwload a file is one thing, validate it is another one. If you separete both, you can test both.
You can test your download system on a generic file (do not have to be a production site), and test if the system works. And you can provide a "fake file" to test the validation, that also do not have to be a production file.
Both tests will give you an idea of how much your code is working as expected.
The XmlDocument.Load(string filename) documentation in MSDN defines the filename
parameter thusly:
filename: URL for the file containing the XML document to load. The URL can be either a local file or an HTTP URL (a Web address).
(emphasis mine)
So your code is entirely testable if your ParseUserDetails
tests pass something like file://C:/path/to/my/test/file
as the request
parameter. They might look something like this:
public void SomeRandomTest()
{
string testFileLocalPath = @"C:\path\to\my\test\file";
// Code to create an XML file with expected data goes here ...
UriBuilder ub = new UriBuilder();
ub.Scheme = "file";
ub.Host = "";
ub.Path = testFileLocalPath;
string request = ub.ToString();
var target = new SomethingThatReadsXml();
var details = target.ParseUserDetails(request, "dummy", "whocares");
// Compare returned user details to expected values here ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With