Saturday, March 17, 2018

Testing PATCH Requests

Like PUT requests, PATCH requests modify an existing record.  But PATCH requests are much tricker to test!  This is because a PUT request modifies an entire record, whereas a PATCH request modifies only one part of the record.  There are many different operations that you can do within a PATCH request: you can add, replace, remove, copy, and move a value in your record.  I'll describe some examples of each and discuss the various ways you can test them.  

Let's use a very simple example of a data record for patching:


The JSON description of the first record would look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": null
}

Notice that our first record does not have a work phone.  Let's do an add operation with our PATCH request in order to add one:

The URL for the request would look something like: https://app/customer/1, where the "1" is representing the id of the customer that you want to patch, and of course the http verb used should be PATCH.  The body of the request would look like this:
[
    {"op":"add","path":"/workPhone","value":"8005551001"}
]
The "op" in this case is referring to the operation that you want to do for your PATCH.  Here we are using an add operation.  The "path" is showing which field you would like to add a value to, in this case the work phone field; the "value" is value that you want to add to that path, in this case the phone number itself.  

When this operation is completed, the JSON description will look like this:

{
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": "8005551001"
}

Here are some ways to test a PATCH add operation:
  • Happy Path- patching where there is currently a null value
  • patching over the null value with an empty value of ""- this should add an empty string
  • patching over an empty value of ""- this should replace the existing empty string
  • patching over an existing value- this will replace the existing value, but ideally the replace operation should be used here instead
  • adding a value with too many or too fewer characters than allowed- an appropriate error message should be returned
  • adding a value with characters that are not allowed- an appropriate error message should be returned
  • adding a value of the wrong type, such as adding an integer when a string is expected- an appropriate error message should be returned
Next, let's take a look at the replace operation.  Let's use the second record this time, and we'll replace the home phone.  This is what the record currently looks like:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551002",
    "workPhone": "8005551003"
}

The URL for the request will be https://app/customer/2, because we are patching the second record, and the body of the request will be:
[
    {"op":"replace","path":"/homePhone","value":"8005551111"}
]
This operation will replace the original home phone of 8005551002 with a phone number of 8005551111, so the record will now look like this:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551111",
    "workPhone": "8005551003"
}

To test a PATCH replace operation, here are some things that you can try:
  • Happy Path- replacing one value with another
  • replacing the value with null- a remove operation would be better for this, but this should still work
  • replacing the value with the empty string ""- this should work
  • replacing a null value with a value- this will probably work, but it would be better to use the add operation
  • replacing with a value with too many or too fewer characters than allowed- an appropriate error message should be returned
  • replacing with a value with characters that are not allowed- an appropriate error message should be returned
  • replacing with a value of the wrong type, such as adding an integer when a string is expected- an appropriate error message should be returned
  • replacing where the existing value is bad in some way, such as having too many characters or having the wrong format- in this case the new good value should be allowed to replace the bad old value
Now let's look at the remove operation.  We'll start with this record:

{
    "id": 1,
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551000",
    "workPhone": "8005551001"
}

To remove the home phone of this customer, we would again use this URL: app/customer/1, and we would use this body:
[
    {"op":"remove", "path":"/homePhone"}
]
We will get this as a result:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": null,
    "workPhone": "8005551001"
}

A remove operation is fairly easy to test.  We just need to verify that the value has indeed been removed and replaced with null.  It's also good to verify that no other value was removed by mistake, such as removing both phone numbers instead of just the home phone. We can also test that it's not possible to remove a required field- this should return an appropriate error message.  

Now let's look at the move operation.  Looking at the previous state of John's record, let's imagine that we have it wrong, and that what we have for his work phone is actually his home phone.  To move the phone number, we would use this request body:
[
    {"op":"move","from":"/workPhone","path":"/homePhone"}
]
In this example, the "from" indicates where the current value is, and the "path" indicates where you would like the value to move to.  After this operation, our record will look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": null
}

Here are some ideas for testing a move operation:
  • Happy Path- moving an existing value to a location where the value is currently null
  • moving an existing value from location A to location B, where there is already a value in location B- in this case, the value in location B will be replaced with the value that was in location A
  • doing a move from A to B where the value of A is null- this should return an appropriate error message, because there's nothing to move
  • doing a move from A to B where the validation constraints are different in location B, so A's value should not be allowed- this should return an appropriate error message
  • doing a move from A to B where the value in A is bad- this should return an appropriate error message
  • moving a value from one location to a location that does not exist: this should return an appropriate error message
Finally, we'll look at the copy operation.  This will copy an existing value and put it in a different location.  Let's take this example:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": null
}

If we want to copy the home phone so that the work phone will be the same number, we can do this request:
[
    {"op":"copy","from":"/homePhone","path":"/workPhone"}
]

The "from" field shows us the location of the value we want to copy, and the "path" field shows us the location we'd like to copy to.  Our record should now look like this:

{
    "id": "1",
    "firstName": "John",
    "lastName": "Smith",
    "homePhone": "8005551001",
    "workPhone": "8005551001"
}

Testing a copy operation is very similar to testing a move operation:
  • Happy Path: copying a value from one location to another where the value is currently null
  • copying a value from location A to location B, where location B currently has a value; this value will be replaced by the value in location A
  • copying from location A where the value of A is null- this should return an error message
  • copying from location A to a location that does not exist- this should return an error message
  • copying from A to B where the validation constraints are different in location B- this should return an error message
  • copying a bad value from location A to location B- this should return an error message
An important thing to note when patching is that PATCH requests can be chained together.  For example, if we start with this scenario:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": "8005551002",
    "workPhone": "8005551003"
}

And we run this PATCH request:
[
    {"op":"remove","path":"/homePhone"},
    {"op":"replace","path":"/workPhone","value":"8005551111"}
]
We will wind up with this result:

{
    "id": 2,
    "firstName": "Amy",
    "lastName": "Jones",
    "homePhone": null,
    "workPhone": "8005551111"
}

When we chain together requests in a PATCH, we need to test that any invalid operation will result in the entire request being invalid.  For example, if we had instead tried this request:
[
    {"op":"remove","path":"/homePhone"},
    {"op":"replace","path":"/workPhone","value":"NOTAPHONENUMBER"}
]
we should receive an error message, and the first part of the PATCH should NOT have been executed.  In other words, we should still have a value in for the home phone.  

As you can see, PATCH requests are almost a set of HTTP verbs in themselves!  They should be used very carefully in an application, because there are so many different ways they can go wrong.  If your application is using them, be sure to test them alone and in combination.  Be sure to test both the Happy Path and the many ways they can fail validation.  If you are able to put bad data into your database, be sure to test patching over bad data.  

In next week's post, I'll discuss the DELETE request, and also how to create a Postman collection!  










1 comment: