Unit testing with mock objects
This article is meant as a short tutorial on how to use an isolation framework. I prefer Moq for my .NET projects, but I'm sure there are other good alternatives out there. Moq can be pulled from NuGet. Simply add it to your test project and you're good to go.
When do we use mocking? The answer is, we use mocking when we need to isolate a unit for testing. Let's say I have a method that pulls a resource from the internet, looks at it and returns a list of strings. What we want to test is how different inputs gives different results, not the actual operation of opening the web page and streaming data.
I have some code ready and I will demonstrate how to write some tests for it. In the code below, I download and go through an xml document and look for nodes containing names:
Separate concerns
Now, the first thing I have to do is to take out the code that talks to the internet. This way, I can inject different result data and see how it affects the output. I simply set up a wrapper for the WebClient, so that the rest of the code can be isolated for testing. Here's my wrapper class:
Here's an implementation of my interface:
Now, I have a separate unit that speeks with the internet and a separate unit that interprets the results. I've set up the wrapper class with an interface so that it can be mocked for testing. Either that or its methods would have to be marked as virtual.
Prep the code
Next, in my web-fetch-thing-class, I set up a new property, WebClientWrapper. The property has the type IWebClientProxy. If it is not set, it will simply create a new instance. This pattern allows me to inject a custom instance of IWebClientProxy for testing.
My FetchData method now looks like this:
Testing the code
Let's take a look at the actual tests. I wanted to test how different results affects how my method aggregates a list of names. Let's start with some code:
In the snippet above, we've created a new instance of a Mock class of the type IWebClientWrapper. We've set up the method FetchXml(...) to take any input, so long as it is of the type String and we've stated that it should return null. Note how we have to do casting when we want to return null. This is because the Return(...) method has overloads, and we have to explicitly tell which one we are using.
Next, have a look at how we inject the mock object. Remember how I made the WebClient wrapper into a property?
Now, I can excute my method, and see how it behaves. I actually want it to throw an exception if the WebClient returns null. Have a look at the full test:
I also want to see how the results are when we provide a valid xml document. Have a look at the following test:
I will have to make some changes in my original code. At least now, It's more testable and I can see how different input data affects the output.
The code is posted on Bitbucket if you want to have a look:
https://bitbucket.org/andersnygaard/webfetcherthingy/src
When do we use mocking? The answer is, we use mocking when we need to isolate a unit for testing. Let's say I have a method that pulls a resource from the internet, looks at it and returns a list of strings. What we want to test is how different inputs gives different results, not the actual operation of opening the web page and streaming data.
I have some code ready and I will demonstrate how to write some tests for it. In the code below, I download and go through an xml document and look for nodes containing names:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public List< string > FetchData( string url) { // Get XmlDocument using WebClient var document = new XmlDocument(); document.Load( new WebClient().OpenRead(url)); // Go through the document and aggregate a list of strings var list = new List< string >(); foreach (var node in document.GetElementsByTagName( "name" )) { list.Add(((XmlNode) node).InnerText); } return list; } |
Separate concerns
Now, the first thing I have to do is to take out the code that talks to the internet. This way, I can inject different result data and see how it affects the output. I simply set up a wrapper for the WebClient, so that the rest of the code can be isolated for testing. Here's my wrapper class:
1
2
3
4
| public interface IWebClientWrapper { XmlDocument FetchXml( string url); } |
Here's an implementation of my interface:
1
2
3
4
5
6
7
| public class WebClientWrapper : IWebClientWrapper { public XmlDocument FetchXml( string url) { // Do stuff and return XmlDocument } } |
Now, I have a separate unit that speeks with the internet and a separate unit that interprets the results. I've set up the wrapper class with an interface so that it can be mocked for testing. Either that or its methods would have to be marked as virtual.
Prep the code
Next, in my web-fetch-thing-class, I set up a new property, WebClientWrapper. The property has the type IWebClientProxy. If it is not set, it will simply create a new instance. This pattern allows me to inject a custom instance of IWebClientProxy for testing.
1
2
3
4
5
6
7
8
9
10
11
| private IWebClientWrapper _webClientWrapper; public IWebClientWrapper WebClientWrapper { get { if (_webClientWrapper == null ) _webClientWrapper = new WebClientWrapper(); return _webClientWrapper; } set { _webClientWrapper = value; } } |
My FetchData method now looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public List< string > FetchData( string url) { // Get XmlDocument from the WebClient wrapper class var document = WebClientWrapper.FetchXml(url); // Go through the document and aggregate a list of strings var list = new List< string >(); foreach (var node in document.GetElementsByTagName( "name" )) { list.Add(((XmlNode) node).InnerText); } return list; } |
Testing the code
Let's take a look at the actual tests. I wanted to test how different results affects how my method aggregates a list of names. Let's start with some code:
1
2
3
4
| var webClientWrapper = new Mock<IWebClientWrapper>(); webClientWrapper .Setup(x => x.FetchXml(It.IsAny< string >())) .Returns((XmlDocument) null ); |
In the snippet above, we've created a new instance of a Mock class of the type IWebClientWrapper. We've set up the method FetchXml(...) to take any input, so long as it is of the type String and we've stated that it should return null. Note how we have to do casting when we want to return null. This is because the Return(...) method has overloads, and we have to explicitly tell which one we are using.
Next, have a look at how we inject the mock object. Remember how I made the WebClient wrapper into a property?
1
2
3
4
| var webFetcher = new WebFetcher { WebClientWrapper = webClientWrapper.Object }; |
Now, I can excute my method, and see how it behaves. I actually want it to throw an exception if the WebClient returns null. Have a look at the full test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| [TestMethod] [ExpectedException( typeof (Exception), AllowDerivedTypes = true )] public void WebFetcher_TestWithNullDocument_ShouldThrowException() { // Setup var webClientWrapper = new Mock<IWebClientWrapper>(); webClientWrapper.Setup(x => x.FetchXml(It.IsAny< string >())).Returns((XmlDocument) null ); var webFetcher = new WebFetcher { WebClientWrapper = webClientWrapper.Object }; // Execute } |
I also want to see how the results are when we provide a valid xml document. Have a look at the following test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| [TestMethod] public void WebFetcher_TestWithValidExampleData_ShouldReturnXmlDocument() { // Setup var document = new XmlDocument(); document.LoadXml( "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><names><name>Brian</name><name>John</name></names>" ); var webClientWrapper = new Mock<IWebClientWrapper>(); webClientWrapper.Setup(x => x.FetchXml(It.IsAny< string >())).Returns(document); var webFetcher = new WebFetcher { WebClientWrapper = webClientWrapper.Object }; // Execute // Assert Assert.IsNotNull(list); Assert.IsInstanceOfType(list, typeof (List< string >)); Assert.IsTrue(list.Count != 0); Assert.IsTrue(list.Contains( "Brian" )); Assert.IsTrue(list.Contains( "John" )); }</ string > |
I will have to make some changes in my original code. At least now, It's more testable and I can see how different input data affects the output.
The code is posted on Bitbucket if you want to have a look:
https://bitbucket.org/andersnygaard/webfetcherthingy/src