Automated HTTP Testing! Part 2 of 2

In part 1:

  • We created the data structure to hold the Person entity and also  a method to compare two people together.
  • Wrote a method for executing a GET request for a HTTP serivice.
  • Wrote a simple GetPerson test.

In this, the second part, we add an additional GET test as well as add functionality for creating data.

Get All

Sometimes, you may want to test a collection of entities. Json.NET provides the ability to serialize/deserialize collections as well. Let’s add a method to the Person entity class for getting a collection.

        public static IEnumerable<Person> GetPeople()
        {
            return JsonConvert.DeserializeObject<List<Person>>(Api.Get(new Uri(Config.Url + "api/person")));
        }

Next, we’ll use this method in our PersonTests and make some assertions.

        [TestMethod]
        public void GetPeople()
        {
            var people = Person.GetPeople();

            Assert.AreEqual(2, people.Count());
            Assert.IsTrue(people.Any(p => p.Equals(new Person
            {
                FirstName = "Joe",
                LastName = "Schmoe",
                Age = 25,
                Married = false,
                BirthDate = new DateTime(1980, 1, 1)
            })));
            Assert.IsTrue(people.Any(p => p.Equals(new Person
            {
                FirstName = "Jon",
                LastName = "Smith",
                Age = 35,
                Married = true,
                BirthDate = new DateTime(1990, 2, 1)
            })));
        }

Post

The only difference between posting and getting data is, instead of deserializing data that we’ve received from the server, we serialize the data we would like to send to the server.

For example, let’s say we would like to add this person to the system:

{
    "firstName":"Jane",
    "lastName": "Smith",
    "age": 35,
    "married": true,
    "birthDate": "03/01/1990"
}

The first thing we need to add is a Post method to the Api class.

        public static String Post(Uri uri, String payload)
        {
            using (var client = new HttpClient())
            {
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
                request.Content = new StringContent(payload.ToString(), Encoding.UTF8, "application/json");

                HttpResponseMessage response = client.SendAsync(request).Result;
                if (response.IsSuccessStatusCode)
                {
                    return response.Content.ReadAsStringAsync().Result;
                }
                else
                {
                    throw new System.Net.WebException(response.Content.ReadAsStringAsync().Result);
                }
            }
        }

This looks similar to the Get method except it takes two parameters. The second parameter is a string payload. This is what gets sent over the wire and is data the application under test will use to create a Person.

Next, let’s add PostPerson to the Person entity class.

        public static String PostPerson(Person person)
        {
            return Api.Post(new Uri(Config.Url + "api/person"), JsonConvert.SerializeObject(person));
        }

This looks similar to the GetPerson method, but instead of deserializing a person returned from the server, this serializes a person to be sent to the server.

With the pieces in place, we can write a test.

        [TestMethod]
        public void PostPerson()
        {
            var person = new Person
            {
                FirstName = "Jane",
                LastName = "Smith",
                Age = 35,
                Married = true,
                BirthDate = new DateTime(1990, 3, 1)
            };

            var response = Person.PostPerson(person);

            //If we were testing a real application, we would
            //execute a GET here in order to validate the POST
            //was successful.
            //Assert.IsTrue(person.Equals(Person.GetPerson("jane")));

            Assert.AreEqual("Person creation success!", response);
        }

You’ll notice the commented lines above. If we were testing a real application (not a mocked one), we would request the newly created ‘Jane’ by executing GET against http://demo3192753.mockable.io/api/person/jane and comparing the actual result with the expected.

Sad path

What if we wanted to test the error conditions of our application? We can do that too. Check out this test for requesting a person that doesn’t exist.

        [TestMethod]
        public void InvalidPerson()
        {
            try
            {
                var response = Person.GetPerson("sally");
                Assert.Fail("Expected 404 error message.");
            }
            catch (System.Net.WebException e)
            {
                Assert.AreEqual("No person exists with specified name.", e.Message);
            }
        }

We expect the Person request to fail, so if the test makes it past that line of code, we should fail the test. Once the Person request fails, the catch asserts that the error message is equal to the error message we expect.

That’s it for automated HTTP testing (for now). Want to see some different tests or have any issues? Just ask!

Automated HTTP Testing! Part 1 of 2

When it comes to testing web applications, I’ve talked a lot about running automated tests against the UI via Selenium. While automate UI tests are fun and important, they should be just part of your automated test suite.

AATP

The picture above is an example of the agile automated test pyramid. The agile automated test pyramid describes the quantities of different types of tests in your test suite.

The specific numbers are unimportant. What’s important to understand is that automated UI tests are brittle and difficult to maintain. It’s best to just use automated UI tests to test the happy path of your application. Automated HTTP tests can be used to test more complex scenarios.

To demo how automated HTTP testing works, I’ve created a new C# project, https://github.com/asphaltpanthers/HelloHttp.

There are 5 steps in writing an automated HTTP test:

  1. Identify and build data structures.
  2. Serialize data structures (POST & PUT).
  3. Execute request.
  4. Deserialize response (GET & GETALL).
  5. Execute assertions.

This post deals with executing a test that GETs data from a HTTP service.

Identify and build data structures

HTTP payloads may consist of either JSON or XML. We will use JSON because it is more widely used and easier to serialize and deserialize.

I’ve created some mock HTTP clients via mockable.io. The first one is a GET request that returns a single person.

http://demo3192753.mockable.io/api/person/joe

returns

{
	"firstName":"Joe",
	"lastName": "Schmoe",
	"age": 25,
	"married": false,
	"birthDate": "01/01/1980"
}

From this we can build our C# data structure.

using System;

namespace HelloHttp.Entities
{
    public class Person
    {
        public String FirstName;
        public String LastName;
        public Int32 Age;
        public Boolean Married;
        public DateTime BirthDate;
    }
}

I created this class in a folder named ‘Entities’ in the HelloHttp project.

Execute request and deserialize response

Since we are executing a GET we can skip the serialize step and go directly to executing the request and then deserializing the response.

To send the request we need a new class. I call this one Api and place it at the root of the project.

using System;
using System.Net.Http;

namespace HelloHttp
{
    public static class Api
    {
        public static String Get(Uri uri)
        {
            using (var client = new HttpClient())
            {
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
                HttpResponseMessage response = client.SendAsync(request).Result;
                if (response.IsSuccessStatusCode)
                {
                    return response.Content.ReadAsStringAsync().Result;
                }
                else
                {
                    throw new System.Net.WebException(response.Content.ReadAsStringAsync().Result);
                }
            }
        }
    }
}

This method accepts a URI and sends it over the wire via a HttpClient. Upon success, it returns the result payload as a string. Upon failure, it throws a WebException with the error text as the exception text.

To parameterize the tests so that they can run on different environments, I added an App.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="Url" value="http://demo3192753.mockable.io/"/>
  </appSettings>
</configuration>

And I reference this key in a class named Config.cs.

using System;
using System.Configuration;

namespace HelloHttp
{
    public static class Config
    {
        public static readonly String Url = ConfigurationManager.AppSettings["Url"];
    }
}

Next, we’ll add a method to the Person class that executes the GET and deserializes it. This code requires you include the Newtonsoft.Json NuGet package (Json.NET). Json.NET is a third party library that provides JSON serialization and deserialization.

        public static Person GetPerson(string name)
        {
            return JsonConvert.DeserializeObject<Person>(Api.Get(new Uri(Config.Url + "api/person/" + name)));
        }

This method executes a request via the Api class. Remember, the Get method returns a string, so we deserialize it into a Person object.

Execute assertions

There’s one more thing to add to the Person class before finally writing a test.

        public Boolean Equals(Person person)
        {
            return String.Equals(FirstName, person.FirstName) &&
                String.Equals(LastName, person.LastName) &&
                Int32.Equals(Age, person.Age) &&
                Boolean.Equals(Married, person.Married) &&
                DateTime.Equals(BirthDate, person.BirthDate);
        }

Although, not required, this method helps when comparing the actual Person to the expected Person.

We’re now ready to write a test. I created a folder named PredefinedScenarios and added this test.

using HelloHttp.Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;

namespace HelloHttp.PredefinedScenarios
{
    [TestClass]
    public class PersonTests
    {
        [TestMethod]
        public void GetPerson()
        {
            var person = Person.GetPerson("joe");

            Assert.IsTrue(person.Equals(new Person
            {
                FirstName = "Joe",
                LastName = "Schmoe",
                Age = 25,
                Married = false,
                BirthDate = new DateTime(1980, 1, 1)
            }));
        }
    }
}

We first request the person “joe” and then assert that the actual “joe” is equal to the expected “joe”.

Congratulations! You’ve written an automated HTTP test!