In this article I will show you how to write unit tests that will test a JSON
WebService.
- This method will accomplish the following:
- It will come in through the front door emulating the calls against the
service in the same manner you will be calling them from your client (web page,
windows app, mobile app)
- It will allow you to specify a strongly typed result object that you will be
able to inspect
- It will allow you to test on all http verbs (GET, POST, PUT, DELETE)
- It will allow you to send post data to mimic a form
- It will allow you to upload files simulating a file input.
To begin I will first create an empty ASP.NET MVC Project and create a single
controller on it to act as our service point. In that controller I will create
a single action that returns a JSON Serialized object.
public class MyServiceController : Controller
{
public JsonResult GetThing()
{
Thing thing1 = new Thing {
Name = "Thing 1",
Description = "Better Than Thing 2"
};
return Json(thing1, JsonRequestBehavior.AllowGet);
}
}
And now if we call this in a browser we will see the following JSON result
Next were going to create a new project in our solution to perform our tests
– this one should simply be a class library. (This is your unit test
project)
Before we create our test were going to build our testing utility that will
allow us to perform the front door tests. Unlike a normal unit test wee not
just going to reference our main project and bang against it’s methods because
we want to see how this thing actually responds to real world requests.
Please note this is not a replacement for unit tests – all methods that power your services
should be under test as well – this is simply a final integration test to make sure it makes it out the front door correctly.
A Simple GET
Since initially were only going to perform a get with no post data were
going to start by simply writing a method that will call the url and get back
the results.
public class HttpTester
{
public static string PerformTest(string url)
{
WebRequest req = WebRequest.Create(url);
WebResponse resp = req.GetResponse();
StreamReader sr = new StreamReader(resp.GetResponseStream());
return sr.ReadToEnd().Trim();
}
}
And to see it in action were going to write a quick unit test.
public class MyServiceTests
{
public string webservicebase = "http://localhost:35772";
[Test]
public void GetThingTest1()
{
var results = HttpTester.PerformTest(webservicebase + "/MyService/GetThing");
Assert.AreEqual(results, "{\"Name\":\"Thing 1\",
\"Description\":\"Better Than Thing 2\"}");
}
}
Running this test passes and as such we now have something to build on.
Return a strongly typed object
The next requirement is we want to be able to tell it we are getting back a
Thing not a String.
To accomplish this were going to introduce a library named fastJSON which has
been developed by Mehdi Gholam and can be found here (with a great article
explaining why it’s awesome) : http://www.codeproject.com/Articles/159450/fastJSON
So now with a little refactoring our PerformTest
method takes on the
following shape
public static string PerformTest(string url)
{
return PerformTest<string>(url);
}
public static ReturnT PerformTest<ReturnT>(string url)
{
WebRequest req = WebRequest.Create(url);
WebResponse resp = req.GetResponse();
StreamReader sr = new StreamReader(resp.GetResponseStream());
var responseString = sr.ReadToEnd();
if (typeof(ReturnT) == typeof(string))
{
return (ReturnT)Convert.ChangeType(responseString, typeof(ReturnT));
}
return fastJSON.JSON.Instance.ToObject<ReturnT>(responseString);
}
And we can now write the following test
[Test]
public void GetThingTest2()
{
Thing results = HttpTester.PerformTest<Thing>(webservicebase + "/MyService/GetThing");
Assert.AreEqual(results.Name, "Thing 1");
Assert.AreEqual(results.Description, "Better Than Thing 2");
}
Running the tests both pass so were still in business and we now have a
pretty handy little tool.
Specify the HTTP Verb
Next were going to want add the ability to specify our HTTP Verb. To do this
we will add an enum to our class and use it control the method our webrequest
uses like so:
public enum HttpVerb { GET, POST, PUT, DELETE };
public static ReturnT PerformTest<ReturnT>(string url, HttpVerb method)
{
WebRequest req = WebRequest.Create(url);
req.Method = Enum.GetName(typeof(HttpVerb), method);
...............
Adding in Post Data
Now we want to add the ability to add postData to the request. To do this we
are going to build on top of a project named ASP.NET Upload: http://aspnetupload.com/
which just removes a lot of the pain of making web calls in C#. It also allows
us to upload files quite easily using C# (which if you have done your self is a
major pain in the a$$). Which is an upcoming requirement for this little
project.
You can either download the source from the site listed above – which you
will need to compile and extract the dll from the bin dir, or you can get the
project file for this demo linked at the bottom which just has the dll in
it.
In order to add in postdata to our little tester here we are going to add in
a NameValueCollection
to our method. Then we are going to modify our request
call to use the upload method from the ASP.NET Upload library we just added to
our project. (yes this step can be accomplished without the third part library,
I’m just planning ahead). The code has been a little refactored and now looks
like this:
public static ReturnT PerformTest<ReturnT>(string url, HttpVerb method, NameValueCollection postData)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = Enum.GetName(typeof(HttpVerb), method);
List<UploadFile> postFiles = new List<UploadFile>();
HttpWebResponse webResponse = HttpUploadHelper.Upload(req, postFiles.ToArray(), postData);
using (Stream s = webResponse.GetResponseStream())
{
StreamReader sr = new StreamReader(s);
var responseString = sr.ReadToEnd();
if (typeof(ReturnT) == typeof(string))
{
return (ReturnT)Convert.ChangeType(responseString, typeof(ReturnT));
}
return fastJSON.JSON.Instance.ToObject<ReturnT>(responseString);
}
}
Now lets add a new an action on our webservice that takes in a form parameter.
To do so we are going back over to our MVC controller and were going to add in
the following:
[HttpPost]
public string SayHello(string name)
{
return "Hello " + name;
}
Notice we are taking in a param and we are forcing it to only respond to a
POST. Now to test we will do the following:
[Test]
public void SayHelloTest1()
{
NameValueCollection postData = new NameValueCollection();
postData["name"] = "Daniel Lewis";
var results = HttpTester.PerformTest(webservicebase + "/MyService/SayHello",
HttpTester.HttpVerb.POST, postData);
Assert.AreEqual(results, "Hello Daniel Lewis");
}
Add the ability to upload files
And with that now passing our next step is to add the ability to upload a
file. Doing so our method now looks like this:
public static ReturnT PerformTest<ReturnT>(string url, HttpVerb method,
NameValueCollection postData, NameValueCollection postedFiles)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = Enum.GetName(typeof(HttpVerb), method);
List<UploadFile> postFiles = new List<UploadFile>();
foreach (var fKey in postedFiles.AllKeys)
{
FileStream fs = File.OpenRead(postedFiles[fKey]);
postFiles.Add(new UploadFile(fs, fKey, postedFiles[fKey], "application/octet-stream"));
}
HttpWebResponse webResponse = HttpUploadHelper.Upload(req, postFiles.ToArray(), postData);
using (Stream s = webResponse.GetResponseStream())
{
StreamReader sr = new StreamReader(s);
var responseString = sr.ReadToEnd();
if (typeof(ReturnT) == typeof(string))
{
return (ReturnT)Convert.ChangeType(responseString, typeof(ReturnT));
}
return fastJSON.JSON.Instance.ToObject<ReturnT>(responseString);
}
}
And to test this we will need a new service action that expects a file, so we
will jump over to our webservice controller and create the following action:
[HttpPost]
public string UploadAFileToMe()
{
if (Request.Files.Count > 0)
{
return "A File Was Uploaded";
}
else
{
return "No File Was Uploaded";
}
}
And we can now perform our test like this:
[Test]
public void UploadAFileToMeTest1()
{
NameValueCollection files = new NameValueCollection();
files["uploaded"] = "Files/NetNinja.PNG";
var results = HttpTester.PerformTest(webservicebase + "/MyService/UploadAFileToMe",
HttpTester.HttpVerb.POST, new NameValueCollection(), files);
Assert.AreEqual(results, "A File Was Uploaded");
}
And with that we now have all the features I initial set out to do.
Extra Credit – A Model Binder
There is one last thing I’d like to add before were done here, and that is
the ability to send in a model instead of a name value collection for the
postdata. This is to match the model binding of ASP.NET MVC. To accomplish
this were going to create a little utility method on HttpTester
class that can
take in any object and convert it into a NameValueCollection
. Obviously it will
use reflection and all it’s going to do is grab all the properties of the object
and then read them into a NameValueCollection
.
public static NameValueCollection ObjectToNameValueCollection(object obj)
{
NameValueCollection results = new NameValueCollection();
var oType = obj.GetType();
foreach (var prop in oType.GetProperties())
{
string pVal = "";
try
{
pVal = oType.GetProperty(prop.Name).GetValue(obj).ToString();
}
catch { }
results[prop.Name] = pVal;
}
return results;
}
To see this in action we will add a new method in our webservice controller that takes in a model.
[HttpPost]
public JsonResult ProcessSomeFakeFormData(FakeFormDataModel formData)
{
Thing thing2 = new Thing {
Name = formData.Name,
Description = string.Format("{0} years old", formData.Age)
};
return Json(thing2);
}
And now we can test our new method like this:
[Test]
public void ProcessSomeFakeFormData()
{
FakeFormDataModel postData = new FakeFormDataModel {
Name = "Daniel Lewis",
Age = 35
};
var results = HttpTester.PerformTest<Thing>(webservicebase + "/MyService/ProcessSomeFakeFormData",
HttpTester.HttpVerb.POST,
HttpTester.ObjectToNameValueCollection(postData));
Assert.AreEqual(results.Name, "Daniel Lewis");
Assert.AreEqual(results.Description, "35 years old");
}
And with that we come to our conclusion. I think you can see from the last test this little utility can be pretty handy and will defiantly help you in testing your webservices.
Final Code
using Krystalware.UploadHelper;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
namespace WebServiceTesting.Tests
{
public class HttpTester
{
public enum HttpVerb { GET, POST, PUT, DELETE };
public static string PerformTest(string url)
{
return PerformTest<string>(url, HttpVerb.GET);
}
public static ReturnT PerformTest<ReturnT>(string url){
return PerformTest<ReturnT>(url, HttpVerb.GET);
}
public static string PerformTest(string url, HttpVerb method)
{
return PerformTest<string>(url, method);
}
public static ReturnT PerformTest<ReturnT>(string url, HttpVerb method)
{
return PerformTest<ReturnT>(url, method, null);
}
public static string PerformTest(string url, HttpVerb method, NameValueCollection postData)
{
return PerformTest<string>(url, method, postData);
}
public static ReturnT PerformTest<ReturnT>(string url,
HttpVerb method, NameValueCollection postData)
{
return PerformTest<ReturnT>(url, method, postData, null);
}
public static string PerformTest(string url, HttpVerb method,
NameValueCollection postData, NameValueCollection postedFiles)
{
return PerformTest<string>(url, method, postData, postedFiles);
}
public static ReturnT PerformTest<ReturnT>(string url, HttpVerb method,
NameValueCollection postData, NameValueCollection postedFiles)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = Enum.GetName(typeof(HttpVerb), method);
List<UploadFile> postFiles = new List<UploadFile>();
if (postedFiles != null)
{
foreach (var fKey in postedFiles.AllKeys)
{
FileStream fs = File.OpenRead(postedFiles[fKey]);
postFiles.Add(new UploadFile(fs, fKey, postedFiles[fKey], "application/octet-stream"));
}
}
HttpWebResponse webResponse = HttpUploadHelper.Upload(req,
postFiles.ToArray(), postData ?? new NameValueCollection());
using (Stream s = webResponse.GetResponseStream())
{
StreamReader sr = new StreamReader(s);
var responseString = sr.ReadToEnd();
if (typeof(ReturnT) == typeof(string))
{
return (ReturnT)Convert.ChangeType(responseString, typeof(ReturnT));
}
return fastJSON.JSON.Instance.ToObject<ReturnT>(responseString);
}
}
public static NameValueCollection ObjectToNameValueCollection(object obj)
{
NameValueCollection results = new NameValueCollection();
var oType = obj.GetType();
foreach (var prop in oType.GetProperties())
{
string pVal = "";
try
{
pVal = oType.GetProperty(prop.Name).GetValue(obj).ToString();
}
catch { }
results[prop.Name] = pVal;
}
return results;
}
}
}
Click Here to download the entire solution
If you like this article you can see more like them at my blog :
http://www.sympletech.com
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.