Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write Functional Tests against ServiceStack API

We have an ASP.NET Web Application wired up with ServiceStack. I've never written functional tests before, but have been tasked to write tests (nUnit) against our API and prove it's working all the way down to the Database level.

Can someone help me get started writing these tests?

Here's an example of a post method on our Users service.

public object Post( UserRequest request )
{
    var response = new UserResponse { User = _userService.Save( request ) };

    return new HttpResult( response )
    {
        StatusCode = HttpStatusCode.Created,
        Headers = { { HttpHeaders.Location, base.Request.AbsoluteUri.CombineWith( response.User.Id.ToString () ) } }
    };
}

Now I know how to write a standard Unit Test, but am confused on this part. Do I have to call the WebAPI via HTTP and initialize a Post? Do I just call the method like I would a unit test? I suppose it's the "Functional Test" part that eludes me.

like image 886
Chase Florell Avatar asked Oct 05 '13 23:10

Chase Florell


1 Answers

Testing the service contract

For an end-to-end functional test, I focus on verifying that the service can accept a request message and produce the expected response message for simple use cases.

A web service is a contract: given a message of a certain form, the service will produce a response message of a given form. And secondarialy, the service will alter the state of its underlying system in a certain way. Note that to the end client, the message is not your DTO class, but a specific example of a request in a given text format (JSON, XML, etc.), sent with a specific verb to a specific URL, with a given set of headers.

There are multiple layers to a ServiceStack web service:

client -> message -> web server -> ServiceStack host -> service class -> business logic

Simple unit testing and integration testing is best for the business logic layer. It's usually easy write unit tests directly against your service classes too: it should be easy to construct a DTO object, call a Get/Post method on your service class, and validate the response object. But these do not test anything that's happening inside the ServiceStack host: routing, serialization/deserialization, execution of request filters, etc. Of course, you don't want to test the ServiceStack code itself as that's framework code that has its own unit tests. But there is an opportunity to test the specific path that a specific request message takes going into the service and coming out of it. This is the part of the service contract that can't be fully verified by looking directly at the service class.

Don't try for 100% coverage

I would not recommend trying to get 100% coverage of all business logic with these functional tests. I focus on covering the major use cases with these tests - one or two reqest examples per endpoint usually. Detailed testing of specific business logic cases is much more efficiently done by writing traditional unit tests against your business logic classes. (Your business logic and data access are not implemented in your ServiceStack service classes, right?)

The implementation

We are going to run a ServiceStack service in-process and use an HTTP client to send requests to it and then verify the content of the responses. This implementation is specific to NUnit; a similar implementation should be possible in other frameworks.

First, you need an NUnit setup fixture that runs one before all of your tests, to set up the in-process ServiceStack host:

// this needs to be in the root namespace of your functional tests
public class ServiceStackTestHostContext
{
    [TestFixtureSetUp] // this method will run once before all other unit tests
    public void OnTestFixtureSetUp()
    {
        AppHost = new ServiceTestAppHost();
        AppHost.Init();
        AppHost.Start(ServiceTestAppHost.BaseUrl);
        // do any other setup. I have some code here to initialize a database context, etc.
    }

    [TestFixtureTearDown] // runs once after all other unit tests
    public void OnTestFixtureTearDown()
    {
        AppHost.Dispose();
    }
}

Your actual ServiceStack implementation probably has an AppHost class that's a subclass of AppHostBase (at least if it's running in IIS). We need to subclass a different base class to run this ServiceStack host in-process:

// the main detail is that this uses a different base class
public class ServiceTestAppHost : AppHostHttpListenerBase
{
    public const string BaseUrl = "http://localhost:8082/";

    public override void Configure(Container container)
    {
        // Add some request/response filters to set up the correct database
        // connection for the integration test database (may not be necessary
        // depending on your implementation)
        RequestFilters.Add((httpRequest, httpResponse, requestDto) =>
        {
            var dbContext = MakeSomeDatabaseContext();
            httpRequest.Items["DatabaseIntegrationTestContext"] = dbContext;
        });
        ResponseFilters.Add((httpRequest, httpResponse, responseDto) =>
        {
            var dbContext = httpRequest.Items["DatabaseIntegrationTestContext"] as DbContext;
            if (dbContext != null) {
                dbContext.Dispose();
                httpRequest.Items.Remove("DatabaseIntegrationTestContext");
            }
        });

        // now include any configuration you want to share between this 
        // and your regular AppHost, e.g. IoC setup, EndpointHostConfig,
        // JsConfig setup, adding Plugins, etc.
        SharedAppHost.Configure(container);
    }
}

Now you should have an in-process ServiceStack service running for all of your tests. Sending requests to this service is pretty easy now:

[Test]
public void MyTest()
{
    // first do any necessary database setup. Or you could have a
    // test be a whole end-to-end use case where you do Post/Put 
    // requests to create a resource, Get requests to query the 
    // resource, and Delete request to delete it.

    // I use RestSharp as a way to test the request/response 
    // a little more independently from the ServiceStack framework.
    // Alternatively you could a ServiceStack client like JsonServiceClient.
    var client = new RestClient(ServiceTestAppHost.BaseUrl);
    client.Authenticator = new HttpBasicAuthenticator(NUnitTestLoginName, NUnitTestLoginPassword);
    var request = new RestRequest...
    var response = client.Execute<ResponseClass>(request);

    // do assertions on the response object now
}

Note that you may have to run Visual Studio in admin mode in order to get the service to successfully open that port; see comments below and this follow-up question.

Going further: schema validation

I work on an API for an enterprise system, where clients pay a lot of money for custom solutions and expect a highly robust service. Thus we use schema validation to be absolutely sure we don't break the service contract at the lowest level. I don't think schema validation is necessary for most projects, but here's what you can do if you want to take your testing a step further.

One of the ways in which you can inadventently break your service's contract is to change a DTO in a way that is not backward compatible: e.g., rename an existing property or alter custom serialization code. This can break a client of your service by making data no longer available or parseable, but you typically can't detect this change by unit testing your business logic. The best way to prevent this from happening is to keep your request DTOs separate and single-purpose and separate from your business/data access layer, but there's still a chance someone will accidentally apply a refactoring incorrectly.

To guard against this, you can add schema validation to your functional test. We do this only for specific use cases that we know a paying client is actually going to use in production. The idea is that if this test breaks, then we know that the code that broke the test would break this client's integration if it were to be deployed to production.

[Test(Description = "Ticket # where you implemented the use case the client is paying for")]
public void MySchemaValidationTest()
{
    // Send a raw request with a hard-coded URL and request body.
    // Use a non-ServiceStack client for this.
    var request = new RestRequest("/service/endpoint/url", Method.POST);
    request.RequestFormat = DataFormat.Json;
    request.AddBody(requestBodyObject);
    var response = Client.Execute(request);
    Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
    RestSchemaValidator.ValidateResponse("ExpectedResponse.json", response.Content);
}

To validate the response, create a JSON Schema file that describes the expected format of the response: what fields are are required to exist for this specific use case, what data types are expected, etc. This implementation uses the Json.NET schema parser.

using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

public static class RestSchemaValidator
{
    static readonly string ResourceLocation = typeof(RestSchemaValidator).Namespace;

    public static void ValidateResponse(string resourceFileName, string restResponseContent)
    {
        var resourceFullName = "{0}.{1}".FormatUsing(ResourceLocation, resourceFileName);
        JsonSchema schema;

        // the json file name that is given to this method is stored as a 
        // resource file inside the test project (BuildAction = Embedded Resource)
        using(var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFullName))
        using(var reader = new StreamReader(stream))
        using (Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFileName))
        {
            var schematext = reader.ReadToEnd();
            schema = JsonSchema.Parse(schematext);
        }

        var parsedResponse = JObject.Parse(restResponseContent);
        Assert.DoesNotThrow(() => parsedResponse.Validate(schema));
    }
}

Here's an example of a json schema file. Note that this is specific to this one use case and is not a generic description of the response DTO class. The properties are all marked as required as these are the specific ones the client are expecting in this use case. The schema might leave out other unused properties that currently exist in the response DTO. Based on this schema, the call to RestSchemaValidator.ValidateResponse will fail if any of the expected fields are missing in the response JSON, have unexpected data types, etc.

{
  "description": "Description of the use case",
  "type": "object",
  "additionalProperties": false,
  "properties":
  {
    "SomeIntegerField": {"type": "integer", "required": true},
    "SomeArrayField": {
      "type": "array",
      "required": true,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "Property1": {"type": "integer", "required": true},
          "Property2": {"type": "string", "required": true}
        }
      }
    }
  }
}

This type of test should be written once and never modified unless the use case it's modeled on becomes obsolete. The idea is that these tests will represent actual usages of your API in production and ensure that the exact messages your API promises to return do not change in a way that breaks existing usages.

Other info

ServiceStack itself has some examples of running tests against an in-process host, on which the above implementation is based.

like image 126
Mike Mertsock Avatar answered Nov 10 '22 11:11

Mike Mertsock