A few days ago, I had an e-mail in my inbox asking about retrieving links between requirements and test cases over REST.
I thought this was a great question and worth a separate post.

I will start by answering the question and then give a little more context around why we have to do what we have to do.

My example test area contains the following items…

As you can see, there is a “requirement based suite” in my plan and a set of normal test suites.
A requirement based suite is generated, when you create test cases on the Kanban board, or if you create a requirements-based suite rather than a static suite.
This is one way of linking test cases to requirements/user stories/PBIs, but not the only one.

All test cases are also work items and can therefore be linked like a work item.
In my static test suite, I have linked one of the cases to a requirement like so:

In “requirements based suites” this link is created automatically.

How can these links (and other information in the work item form) be retrieved?

In a previous blog post I pointed out that you can use the REST API to work your way through the Test area in code.

In the below example I am starting by looking up my test plans. I then drill down into my requirements-based suite, hoping to find the link.

# set credentials
$pat = "{insertPATTokenHere}"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "",$pat)))

# get plans
(Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/{projectName}/_apis/test/plans").value

# get a particular plan
Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/{projectName}/_apis/test/plans/33"

# get all suites in that plan
(Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/{projectName}/_apis/test/Plans/33/Suites").value

# get test cases for a particular suite
(Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/{projectName}/_apis/test/Plans/33/Suites/342/testcases").value.testCase

# get test case detail
(Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/_apis/wit/workItems/341")

The output of the test cases (second to last step) is a list of test cases and their work item URLs.
In the last step I am looking up a particular work item ID for a test case. (the work item ID will always match the test case ID, because work items are used as the backend data storage for test cases)

This gives me the following data: (All fields are arrays with more data)

This is useful information. I can look up the work item fields (for example: description) and see what has changed over time (by drilling into revisions). Crucially though I do not get work item links.
That is because VSTS does not store them with the work item itself but in a different part of its database. (And in a very different way for that matter)

We can pull the history of all work item links by using this API call.

To filter just test related references for a particular item, I built the following in PowerShell.

$wit = 341
$links = (Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET -Uri "https://{accountName}.visualstudio.com/_apis/wit/reporting/workitemlinks?linkTypes=Microsoft.VSTS.Common.TestedBy").values

foreach($link in $links) {
if($link.isActive -eq $false) {
if($link.targetId -eq $wit) {
Write-Host $link.sourceId "has a link to" $wit

This produces the output: 22 has a link to 341

A quick check of 341 reveals that this is true:

I could now obviously use the previously discussed work item call to make my PowerShell richer and look up both work items to display their titles instead of their IDs. I could also display further information on any of the other fields in these work items.

If you are considering using the call, make sure you do as much server-level filtering as you can. (The GET parameters allow for a few options) This will significantly reduce your payload and improve performance. It is a good idea to specify a start time, as you even get previous links back that do not exist anymore.

Why is this process not more straightforward?

I said I was going to cover off the technical reasons why things are how they are at the end; so here goes.

VSTS is the cloud version of TFS, which has been around for quite a while. (in software terms anyway) Early in the TFS life-cycle the concept of a work item was introduced. Workitems are stored in the TFS database and are optimised for quick retrieval, allow for various customisations, and work great for reporting.

When the manual test capability was first implemented, work items were chosen as the underlying data storage.

The problem with that is that they do not fully behave like work items. For example: If you use the Excel plugin to load them into Excel and modify the steps (stored as XML/HTML), this breaks the step definition.

Because tests are work items, information can be stored in their work item dialogue, and links can be created against them. This is intentional and works as expected.
Test cases have a different type of link as well – however – the link to their suite or plan. This link is not visible in the work item form at all and is explored completely differently (as shown above) over REST than normal work item links. This can be confusing to new users.

The upside of storing tests as work items and not in an external data store, means that they get backed up with the rest of TFS. Implementing the manual test back end in this way has reduced complexity in TFS and allows for better integration (via the board and with release management for example).