0

I'm working on an application which contains an element that needs to make several http calls in a set order, to perform an action successfully. (Interfacing with an external system's API.)

We have written code that is functional, but struggled when trying to write appropriate unit tests to cover it (not least the HTTPClient). That made me question this smell and wonder if there's a better way of skinning it, perhaps a suitable design pattern.

We make a request to the other system to update a status and note. But to do that we have to do a few get's first, to get the object to modify. We also have to use this approach for some other operations, which we haven't written yet, such as CreateNewRequest(params), so keen to find a better approach.

  • They always need to run in order.
  • If one fails then the whole lot might not know until the end item that checks the 'put's response. In which case it'd always have to start from the beginning again.

Which object oriented design pattern should be best suitable for this scenario (and unit testable)?

Here's a sample of what we are doing at the moment incase that helps.

async Task<bool> ChangeRequestStatusAsync(HttpClient client, int requestId, RequestStatus statusToChangeTo, string note)
    {
        var actions = await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/action");
        int nextActionId = ExtractNextActionId(actions);
        int previousActionId = ExtractNextActionId(actions);

        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/action/{previousActionId}");
        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/step/taskdefinitions");
        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/request/{nextActionId}");

        string requestsJson = await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/step/tasks");

        await client.GetStringAsync($"{client.BaseAddress}/controller/workflow/request/{nextActionId}");

        //Send back the modified requests list
        var modifiedJsonRequest = ModifyRequestStatus(requestId, statusToChangeTo, note, requestsJson);
        HttpContent requestToPut = new StringContent(modifiedJsonRequest, Encoding.UTF8);
        requestToPut.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        var requestPut = await client.PutAsync($"{client.BaseAddress}/controller/workflow/step/tasks", requestToPut);

        //TODO: Check for success and return
    }
  • 2
    Just glancing at it, it looks like you'll need to be able to mock HttpClient in order to make this unit testable: stackoverflow.com/questions/36425008/… – Eternal21 Feb 22 '18 at 12:56
  • I'm in the process of that now. that aside, I still wonder whether there is a suitable pattern that would be helpful in structuring my code for this. – David C Feb 22 '18 at 15:49
4

That made me question this smell and wonder if there's a better way of skinning it, perhaps a suitable design pattern.

A common pattern here is to recognize that there is a boundary between your core and the thing-over-there-that-answers-your-http-requests. There is a protocol that the two need to agree upon.

Trying to achieve the usual properties of a unit test (fast, deterministic, isolated) when talking across process boundaries is hard.

A way to cheat this is to write your unit tests such that the protocol doesn't cross the process boundary. This is usually done by replacing the connection to the remote process with a test double that responds in a controlled fashion.

Of course, when you do this, the unit tests are establishing that the system under test is compatible with the test double, rather than the remote process. You can write tests that cover this gap, but these tests are typically run at a different part of the life cycle (integrated tests, integration tests, system tests, end to end tests....)

If you look carefully, you may notice that the test double acts as a different strategy for providing protocol responses. This naturally feeds into a ports-and-adapters approach, where the client code talks to some interface, and you "inject" the appropriate implementation strategy for the current context (using the test double in a test process, and accessing the remote process in others).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.