Unity Test Runner with HTTP mocking
Executing the unit test with the mocked HTTP client

Recently, we faced the problem of creating unit tests for our Unity tracking SDK (GitHubRAGE Framework | Introduction). The SDK enables game developers to transmit telemetric data to the goedle.io tracking API. To us, it is of great importance to ship reliable software. On the one hand, our customers have to trust our SDKs. On the other hand, if our SDKs or other components crash, we lose data and our algorithms only have access to incomplete data. Test-driven development cannot guarantee an error-free software but it helps a lot to develop high-quality software.

In my opinion, when I started coding for Unity, I had the impression that unit testing in Unity can be very complicated, especially after the Unity Test Tools are no longer available. A very helpful introduction into unit testing in Unity can be found on the Unity Blog. As well as a really great introduction into unit tests with NSubstitute in Unity can be found here. We were searching for an option to test keys and values in a JSON object as part of the as payload sent to our API. We were not able to mock these HTTP request with the UnityWebRequest with NSubstitute. We also didn’t find an applicable solution to stub the HTTP requests.

Our first step was to look out for HTTP mocking tools. Unity is always special because of the .NET runtime 3.5. One is able to use C# 6 with .NET 4.6 runtime but this does currently not hold for the default case and is experimental in Unity. A lot of Unity projects are based on the .NET 3.5 runtime. Our SDK, which should support as many applications as possible, has to be compatible with mainstream Unity projects. Therefore, we are already limited in the frameworks which are out there. Indeed, there are frameworks which could mock HTTP requests in C#, also for older .NET runtime versions. However, Unity does not natively support the C# HTTP request and we wanted to use the UnityWebRequest because we don’t want to have too many dependencies in our SDK. Popular mocking frameworks like WireMock or HTTPMock have support for .NET runtime 3.5, but they cannot mock the UnityWebRequest. These libraries only support the standard C# HTTP request, which isn’t part of Unity. There were also additional requirements for asserts we wanted to have so that we can test the following attributes and components:

  • Response codes
  • HTTP header
  • HTTP errors
  • Payload

Even after a lot of research, we didn’t find a suitable solution to mock an HTTP client based on UnityWebRequest – if you know one, don’t hesitate to reach out to us. Fortunately, Nsubstitute and writing our own wrapper classes and new interfaces helped us to solve the problem.

Why is it necessary?

The overall objective was to test the HTTP communication of our Unity tracking SDK. The SDK sends tracking data via a POST request to the goedle.io backend.

So we needed to mock certain methods and attributes of the UnityWebRequest class, but the class has no interface. The solution was to write a wrapper class, with an interface which we can mock. We implemented the methods which we were using in our SDK and added them to the interface. In the class, we used these functions as a wrapper. The first example case only tests if the SendWebRequest() method is executed.

Simple Example

Let’s have a look at a minimal example of a basic Unity HTTP client class:

public class BasicHttpClient : MonoBehaviour
{
    public void sendPost(string content)
    {
        StartCoroutine(basicPostClient(content));
    }
    public IEnumerator basicPostClient(string content)
    {
        UnityWebRequest unityWebRequest = new UnityWebRequest();
        using (unityWebRequest)
        {
            unityWebRequest.method = "POST";
            unityWebRequest.url = "example.com"; // Insert your URL variable/string here
            byte[] byteContentRaw = new UTF8Encoding().GetBytes(content);
            unityWebRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(byteContentRaw);;
            unityWebRequest.unityWebRequest.SetRequestHeader("Content-Type", "application/json");
            unityWebRequest.chunkedTransfer = false;
            yield return unityWebRequest.SendWebRequest();
            if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
            {
                Debug.LogError("isHttpError: " + unityWebRequest.isHttpError);
                Debug.LogError("isNetworkError: " + unityWebRequest.isNetworkError);
            }
        }
    }
}

Due to a missing interface of the UnityWebRequest class, we need to add a few code changes and write our own wrapper class:


public interface IGoedleWebRequest
{
    bool isHttpError { get; }
    bool isNetworkError { get; }
    string url { get; set; }
    long responseCode { get; }
    string method { get; set; }
    bool chunkedTransfer { get; set; }
    DownloadHandler downloadHandler { get; set; }
    UploadHandler uploadHandler { get; set; }
    UnityWebRequest unityWebRequest { get; set; }
    UnityWebRequestAsyncOperation SendWebRequest();
    void SetRequestHeader(string name, string value);
}

public class GoedleWebRequest : IGoedleWebRequest
{

    UnityWebRequest _unityWebRequest { get; set; }

    public bool isNetworkError
    {
        get { return _unityWebRequest.isNetworkError; }
    }

    public bool isHttpError
    {
        get { return _unityWebRequest.isHttpError; }
    }

    public string url
    {
        get { return _unityWebRequest.url; }
        set { _unityWebRequest.url = value; }
    }

    public string method
    {
        get { return _unityWebRequest.method; }
        set { _unityWebRequest.method = value; }
    }

    public UnityWebRequest unityWebRequest
    {
        get { return _unityWebRequest; }
        set { _unityWebRequest = value; }
    }

    public UploadHandler uploadHandler
    {
        get { return _unityWebRequest.uploadHandler; }
        set { _unityWebRequest.uploadHandler = value; }
    }

    public UnityWebRequestAsyncOperation SendWebRequest()
    {
        return _unityWebRequest.SendWebRequest();
    }

    public void SetRequestHeader(string name, string value)
    {
        _unityWebRequest.SetRequestHeader(name, value);
    }

    /*
    You can add here additional wrapping attributes or methods from the interface you want to mock e.g the download handler or the response code
    */
}

Et voila: So now we are able to mock or check the execution of SendWebRequest. We have to adjust the basic example of the HTTP client so we can make use of the interface:


public class BasicHttpClient : MonoBehaviour
{

    public void sendPost(string content, IGoedleWebRequest goedleWebRequest)
    {
        StartCoroutine(basicPostClient(content, goedleWebRequest));
    }

    public IEnumerator basicPostClient(string content, IGoedleWebRequest goedleWebRequest)
    {
        goedleUploadHandler.add(content);
        goedleWebRequest.unityWebRequest = new UnityWebRequest();
        using (goedleWebRequest.unityWebRequest)
        {
            goedleWebRequest.method = "POST";
            goedleWebRequest.url = "example.com"; // Insert your URL variable/string here
                                                  // initially, this may look a bit strange -> goedleUploadHandler.uploadHandler; we will later explain why we do it this way
            goedleWebRequest.uploadHandler = goedleUploadHandler.uploadHandler;
            goedleWebRequest.unityWebRequest.SetRequestHeader("Content-Type", "application/json");
            goedleWebRequest.chunkedTransfer = false;
            yield return goedleWebRequest.unityWebRequest.SendWebRequest();
            if (goedleWebRequest.isNetworkError || goedleWebRequest.isHttpError)
            {
                Debug.LogError("isHttpError: " + goedleWebRequest.isHttpError);
                Debug.LogError("isNetworkError: " + goedleWebRequest.isNetworkError);
            }
        }
    }
}

A test with Nsubstitute would now look like the following example:


[Test]
public void checkBehaviorWebRequestSendPost()
{
    BasicHttpClient http_client = (new GameObject("BasicHttpClient")).AddComponent();
    IGoedleUploadHandler goedleUploadHandler = Substitute.For();
    IGoedleWebRequest goedleWebRequest = Substitute.For();
    string stringContent = null;
    // Here we write the value which is passed through the add function to stringContent
    goedleUploadHandler.add(Arg.Do(x => stringContent = x));
    // isHttpError and isNetworkError can be set to false, because we want the behavior from a error free request
    goedleWebRequest.isHttpError.Returns(false);
    goedleWebRequest.isNetworkError.Returns(false);
    http_client.sendPost("{\"test\": \"test\"}", goedleWebRequest, goedleUploadHandler);
    var result = JSON.Parse(stringContent);
    Assert.AreEqual(result["test"].Value, "test");
}

If you want to check the content which is sent, you have to wrap the upload handler so one can be sure that the data which is sent has the right format. Therefore, we created another interface and class:


public interface IGoedleUploadHandler
{
    UploadHandler uploadHandler { get; set; }
    void add(string stringContent);
    string getDataString();
}

public class GoedleUploadHandler : IGoedleUploadHandler
{
    UploadHandler _uploadHandler { get; set; }

    public void add (string stringContent)
    {
        byte[] byteContentRaw = new UTF8Encoding().GetBytes(stringContent);
        _uploadHandler = (UploadHandler)new UploadHandlerRaw(byteContentRaw);
    }
    public string getDataString()
    { 
        return Encoding.UTF8.GetString(_uploadHandler.data);
    }
    public UploadHandler uploadHandler
    {
        get { return _uploadHandler; }
        set { _uploadHandler = value; }
    }
}

You can apply the same technique to the download handler, and enhance the methods you want to mock from the web request. This was a quick introduction to unit testing web requests in Unity. You can also find an example project here.

Summary

So far, this has been the fastest way for us to create unit tests for a Unity HTTP client which uses the UnityWebRequest. In general, unit testing in Unity has been cumbersome. Especially when it comes to testing asynchronous behavior. We highly appreciate recommendations and suggestions to improve our test environment. Or, if there are any frameworks for Unity which we have overseen, please drop us an email or leave a comment. Until then, we keep our fingers crossed that Unity will enhance the unit testing capabilities in the near future.

To learn more what goedle.io offers for developers of games, please visit http://www.goedle.io/gaming or reach out to us. And don’t forget to subscribe to our newsletter and follow us on Twitter.