Image courtesy of Scott Shiller
Over the past week or so, I’ve been working on some code that will query a RESTful service. As I was writing this code, I separated the network communication from the processing of the content. In my mind, this is a good isolation of the two concerns. The challenge that I faced, was how do I test the communication with these external services?
I am a unit testing fanatic. I have had horror stories of getting to the final few days of a project and one of my quality assurance analysts informing me that a bug was found in a block of code that wasn’t directly touched. I’ll go into my thoughts on TDD, BDD, and the other testing practices in another post…
How should I test a block of code that is dependent on a third party resource? Isolate the resource, and ‘mock’ it. Typically, this approach is handled through a Mocking library. Personally, I prefer the Moq library… but in this case, Moq isn’t going to completely handle my problem.
The following code has been sanitized to protect the innocent. Consider the following simple classes to request data from the service:
/// /// A simple DTO /// public class MyOrder { public int Id; public string CustomerName; public decimal OrderTotal; } public class ClientConnector { public Uri ServiceLocation = new Uri("http://api.samplerestfulservice.com"); public IEnumerable GetOrders() { var req = WebRequest.Create(ServiceLocation); WebResponse resp = null; resp = req.GetResponse(); var js = new JavaScriptSerializer(); var sr = new StreamReader(resp.GetResponseStream()); var orders = js.Deserialize(sr.ReadToEnd()); return orders; } }
Note the referenced service location on line 16. This is where we can start injecting our Mock service.
I have used the NancyFX web server in the past for small projects where I needed to host a WebUI inside of an application as a ‘remote console’ for an administrator. In this case, we can use the Nancy service as a mock host for our unit tests to demonstrate that the service is properly communicating with the service provider.
In a unit test project, we can add the following NuGet references to get started:
Now we can start a TestFixture class with a Nancy service started and maintained for the duration of the fixture:
[TestFixture] public class SimpleFixture { private NancyHost _Nancy; protected Uri _MyUri = new Uri("http://localhost:50001"); [TestFixtureSetUp] public void FixtureSetup() { _Nancy = new NancyHost(_MyUri); // Need to retry in order to ensure that we properly startup after any failures for (var i = 0; i < 2; i++) { try { _Nancy.Start(); break; } catch { try { _Nancy.Stop(); } catch (Exception e) { Console.Out.WriteLine("Exception setting up: " + e); } } } // Prime the connection var req = WebRequest.Create(_MyUri); req.GetResponse(); } [TestFixtureTearDown] public void FixtureTeardown() { try { _Nancy.Stop(); _Nancy = null; } catch { } } public class StubModule : NancyModule { public static bool _Connected = false; public StubModule() { Get["/"] = _ => { _Connected = true; return "[]"; }; } } }
Check out line 7, this is where I have defined the base URI for the Nancy webserver to listen on. It is important that the port to be listened on be in the 50000 range, as these network ports are all marked for private use.
In Nancy, we write NancyModule classes that define how to respond to Http Requests. The StubModule written in this code defines a single route to return data on. At line 60 you’ll see where I have configured this initial Get request handler to return a pair of brackets. For this sample service, it is expecting an array of JavaScript objects to be returned, so the square brackets in the StubModule indicate an empty JavaScript array. To wire-up a test for my simple method, it might look something like this:
[Test] public void WhenNoOrdersAreAvailableShouldReturnEmptyOrdersCollection() { // Arrange var connector = new MyRestClient.ClientConnector { ServiceLocation = _MyUri }; // Act var result = connector.GetOrders(); // Assert Assert.IsTrue(StubModule._Connected, "Did not request data from the service"); Assert.IsNotNull(result, "Did not return data from Connector - at a minimum we should get an Empty collection"); Assert.AreEqual(0, result.Count(), "Returned items in the array, when none were transmitted"); }
Now I have a simple test that can be used to verify that my RESTful client made a connection to the Nancy service and returned successfully. Other tests with mocked responses from Nancy can be written and stubbed results can be further defined in the StubModule.