C# Advent: WireMock.Net

C# Advent: WireMock.Net

For the past few years I have mostly been testing backend services written in Java. But now that I’ll be working more in Unity, I wanted to see if one of my favorite Java testing libraries, WireMock, is available for C# development. WireMock allows you to set up a mock HTTP service at run-time with request matching and pre-set responses. If you need to write tests with any kind of micro-service or other HTTP dependencies, then WireMock is an invaluable tool for setting up fast and reliable mock responses. Thankfully, a port is available for .Net development: WireMock.Net!

If you have used the original WireMock, then WireMock.Net is very similar. But if you haven’t, here is a quick overview of how it works.

First let’s set up a little bit of code to test (Full code available here):

namespace WireMockSample
{
    public class SimpleServiceCaller
    {
        private readonly string _baseUrl;
        public SimpleServiceCaller(string baseUrl){
            _baseUrl = baseUrl;
        }
        public SimpleLoginResponse DoSimpleLogin(string userName)
        {
            var client = new RestClient(_baseUrl);
            var request = new RestRequest("/player/startSession", Method.POST);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(new SimpleLoginRequest
            {
                userName = userName
            });
            var response = (RestResponse)client.Execute(request);
            SimpleLoginResponse loginResponse = null;
            if (response.IsSuccessful)
            {  
                loginResponse = JsonConvert.DeserializeObject(response.Content);
            }
            else
            {
                if (response.StatusCode.Equals(HttpStatusCode.Forbidden))
                {
                    loginResponse = new SimpleLoginResponse
                    {
                        error = "Forbidden User"
                    };
                }
            }
            

            return loginResponse;
        }
    }

    public class SimpleLoginRequest
    {
        public string userName;
    }

    public class SimpleLoginResponse
    {
        public int id;
        public string userName;
        public string error;
    }
}

This is just a simple class that uses RestSharp to make a POST request to another service. A simple integration test might look like this:

[Test]
[Ignore("only works on my machine")]
/*
 * This will only work if you happen to
 * have a version of the target service
 * running at localhost port 8787
 */
public void TestSimpleLogin()
{
    var caller = new SimpleServiceCaller("http://localhost:8787");
    var userId = Guid.NewGuid().ToString().Replace("-", "");
    var response = caller.DoSimpleLogin(userId);
    Assert.AreEqual(userId, response.userName);
}

The problem with this, of course is that it depends on the actual service running somewhere, which is not practical for unit testing or a CI pipeline. But WireMock.Net is here to help us out!

Setup

The first step is to download and install the package from NuGet. Then add the following using directives:

using WireMock.Matchers;
using WireMock.Server;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;

Starting the service is very simple. By default, WireMock dynamically allocate its own ports, so you will need to get the generated URL to pass to your calling object.

private static FluentMockServer _mockServer;
private static SimpleServiceCaller _simpleCaller;

[TestFixtureSetUp]
public static void SetUpWireMock()
{
    _mockServer = FluentMockServer.Start();
    // WireMock selects its own ports, so just grab the first
    // generated URL string
    _simpleCaller = new SimpleServiceCaller(_mockServer.Urls.First());
}

You only need to have one running service for each TestFixture.

Request Matching

Once the mock server is running, you can set up stub responses based on a variety of request matching strategies. In this case, the StubLogin method is using a JsonMatcher, which matches incoming request against a given JSON object. Requests can also be matched against the path, query parameters, or header values.

private static void StubLogin(string testUser, int expectedId)
{
    var canned = new SimpleLoginResponse
    {
        userName = testUser,
        id = expectedId,
        currentLevel = 0,
        maxLevel = 1

    };
    _mockServer
          .Given(Request
            .Create().WithPath("/player/startSession")
            // The canned response will *only* be sent to the 
            // request with the corresponding userName
            .WithBody(new JsonMatcher("{ \"userName\": \"" + testUser + "\" }"))
            .UsingPost())
          .RespondWith(
            Response.Create()
              .WithStatusCode(200)
              .WithHeader("Content-Type", "application/json")
            .WithBodyAsJson(canned)
          );

}

WireMock.Net also supports Linq, Regex, and XPath matching along with several others. Unfortunately, WireMock.Net has not caught up to the Java version in that custom request matchers are not supported. Still, the provided matchers will cover most common service scenarios. More information on WireMock.Net request matchers here.

Testing with WireMock.Net

Once your service is set up, you can use your mock service just as if it was the real running service.

Basic Test

[Test]
/*
 * This will work anytime, anywhere!
 */
public void TestSimpleLoginWireMock()
{
    var userId = Guid.NewGuid().ToString();
    StubLogin(userId, 42);
    var response = _simpleCaller.DoSimpleLogin(userId);
    Assert.AreEqual(userId, response.userName);
    // predictable id!
    Assert.AreEqual(42, response.id);

}

HTTP Failure Test

Testing if your code works when other services fail is easy with WireMock.Net!

[Test]
/*
 *  Mocking error conditions is simple
 */
public void TestLoginFailureWireMock()
{
    // Note that because the failure is tied to a specific userName,
    // the other tests will still function properly
    _mockServer
        .Given(Request
            .Create().WithPath("/player/startSession")
            // The canned response will *only* be sent to the 
            // request with the corresponding userName
            .WithBody(new JsonMatcher("{ \"userName\": \"BADWOLF\" }"))
            .UsingPost())
        .RespondWith(
            Response.Create()
                .WithStatusCode(HttpStatusCode.Forbidden)
        );
    
    
    var response = _simpleCaller.DoSimpleLogin("BADWOLF");
    Assert.AreEqual("Forbidden User", response.error);

}

Note that you can also set up a scenario where all requests are accepted *except* the failure case by using MatchBehaviour.RejectOnMatch. 

Other Testing Scenarios

  • Unit testing a client when the services it depends on aren’t ready yet
  • Fast UI testing with mocked service calls
  • Setting up test players with mock authentication
  • Simulating slow response or service outages
  • WireMock.Net.Standalone as a stand-alone mock service for CI/CD pipelines
  • Many many more!

I’ve used WireMock for many years in testing Java services, so I’m very happy that it is available for DotNet. I hope you will take a look and see how it can expand your testing repertoire as well!

 

2 thoughts on “C# Advent: WireMock.Net

Leave a comment