Click here to Skip to main content
15,860,972 members
Articles / Web Development / ASP.NET

Strongly Typed Testing Against JSON Web Services in C#

Rate me:
Please Sign up or sign in to vote.
4.60/5 (7 votes)
10 Oct 2012CPOL5 min read 25.9K   21   2
Article describing how to test JSON Webservices using C# so you can test your webservices in your unit tests

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.

C#
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. 

C#
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. 

C#
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 

C#
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 

C#
[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: 

C#
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: 

C#
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: 

C#
[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: 

C#
[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: 

C#
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: 

C#
[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: 

C#
[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.  

C#
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.

C#
[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: 

C#
[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 

C#
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 };

        //-- URL only Get Assumed

        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);
        }

        //URL and Request Type without any postData

        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);
        }

        //URL, request type, and postData

        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);
        }

        //URL, request type, postData and files

        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);
            }
        }

        //-- Convert any object into a name value collection
        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Sympletech
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionNice Article Pin
Santhakumar Munuswamy @ Chennai9-May-15 20:49
professionalSanthakumar Munuswamy @ Chennai9-May-15 20:49 
GeneralMy vote of 4 Pin
J. Wijaya10-Oct-12 15:05
J. Wijaya10-Oct-12 15:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.