Using Pester for API Testing

Pester is rapidly becoming my go-to tool for writing tests that are outside of traditional C#/NUnit/Visual Studio kinds of tests. Things like snippets of my own PowerShell code (obviously) but also testing, say, a third-party tool to make sure how we use it isn’t broken by us taking an upgrade to the tool or the operating system it runs on.

Over the last few months I have been writing a PowerShell module to make testing ReST APIs easier with Pester so that a less technical team could follow by example existing tests and write new tests for new resources with the minimal of boilerplate and implementation details.

Using Pester I saw this as three components:

  • the PowerShell API module
  • the Pester startup script
  • the resource test script(s) discovered at runtime by Pester

The API module has methods to do basic CRUD operations against a resource, plus utilities to set the base url, optional headers etc. The utilities are there so that the admin/gardening of setting up the api with authentication, picking the right endpoint etc. can be done once in the startup script and the test scripts aren’t repeating information or having to use methods not related to the task at hand.


Set-StrictMode -Version Latest

[string]$script:BaseUrl =  'https://swapi.co/api/'
[hashtable]$script:Headers = @{ }


Function Set-ApiBaseUrl {
    [CmdletBinding()]
    Param (
		[Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
		[string]$Url
    )
	
	Write-Verbose "Changing api url to $Url"
	
	$script:BaseUrl = $Url
}


Function Set-ApiHeaders {
    [CmdletBinding()]
    Param (
		[Parameter(Mandatory=$True)]
		[hashtable]$Headers
    )

	$script:Headers = $Headers
}


Function Get-ApiResource {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True, Position=0)]
        [string]$Resource
    )

    $Response = Invoke-RestMethod -Method Get -Uri "$($script:BaseUrl)$Resource" -Headers $script:Headers

    Write-Output $Response
}


Function Add-ApiResource {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [string]$Resource,

    	[Parameter(Mandatory=$True, Position=1)]
	    [hashtable]$Body
    )

    $Json = $Body | ConvertTo-Json
    $Response = Invoke-RestMethod -Method Post -Uri "$($script:BaseUrl)$Resource" -Headers $script:Headers -Body $Json -ContentType 'application/json'

    Write-Output $Response
}


Function Set-ApiResource {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [string]$Resource,

    	[Parameter(Mandatory=$True, Position=1)]
	    [hashtable]$Body
    )

    $Json = $Body | ConvertTo-Json
    $Response = Invoke-RestMethod -Method Put -Uri "$($script:BaseUrl)$Resource" -Headers $script:Headers -Body $Json -ContentType 'application/json'

    Write-Output $Response
}


Function Remove-ApiResource {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True, Position=0)]
        [string]$Resource
    )

    $Response = Invoke-RestMethod -Method Delete -Uri "$($script:BaseUrl)$Resource" -Headers $script:Headers

    Write-Output $Response
}


I can now define a smoke test script which we can run to set everything up prior to running each Pester test.



[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$here = Split-Path -Path $MyInvocation.MyCommand.Path
Import-Module $here\RestAPI.psm1
Import-Module Pester

$BaseUrl = 'https://swapi.co/api/'

Set-ApiBaseUrl -Url $BaseUrl

Invoke-Pester "$here\*.Tests.ps1" -Verbose:$VerbosePreference 


Which makes for a reasonably compact but focussed test which can be written by a team member without what they see as unecessary “plumbing” on show. Note here I am using the Star Wars API to avoid bogging down the example tests in archaic dommains. You could use any number of test APIs, like Game of Thrones, Dog API, Ron Swanson quotes or Dad jokes



Describe 'Star of A New Hope' {

    It 'Is Luke' {

        $Character = Get-ApiResource 'people/1/'

        $Character.name | Should Be 'Luke Skywalker'
    }

}




Describe 'Films' {

    It 'Correct Number of Films' {

        $Films = Get-ApiResource 'films/'

        $Films.Count | Should Be 7
    }

}