Testing with Pester – Getting Started

I’ve been using Pester now for around 4 months now and I can’t imagine developing any significant PowerShell code without it. It’s firmly in my tool belt for developing robust modules and has forced me to consider the way my modules are structured. At this point though I wouldn’t say I follow a strict TDD approach but the more PowerShell development I do the more I think there would be value to this. At the very least I try to ensure that for every cmdlet I develop I at least attempt to put in place a corresponding Pester test to exercise it.

Pester describes itself as the “…ubiquitous test and mock framework for PowerShell” and so far I can testify that my experience using it has been largely pain-free. So how do we go about testing a block of PowerShell code? Here’s a simple function we want to test:

function Add-ClientNameToDatabase {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$clientName,
        [Parameter(Mandatory = $true)]
        [hashtable]$settings
    )

    process {
        Write-Information "Adding Client Name [$clientName] into database"
        $sql = "INSERT into dbo.Clients VALUES($clientName);"
        Invoke-Sqlcmd -ServerInstance $settings.TargetServer -Database $settings.TargetDatabase -Query $sql
    }
}

And here is an example of the contents of a very simple test (named Add-ClientNameToDatabase.tests.ps1 which exists in a folder name Tests)

$currentLocation = Get-Location
$folderPath = Join-Path -Path $currentLocation -ChildPath '\Private'
 
. "$folderPath\Add-CLientNameToDatabase.ps1";

Describe "Add-ClientNameToDatabase" {
    Mock Invoke-Sqlcmd {
        return @{
            result=1
        }
    }   
    It 'calls Invoke-SQL at least once in the It block' {
        $settings = @{ }
        $settings["TargetDatabase"] = "_TargetDatabase"
        $settings["TargetServer"] = "_TargetServer"

        Add-ClientNameToDatabase -clientName "TestClient" -settings $settings
        Assert-MockCalled Invoke-Sqlcmd -Exactly 1 -Scope It
    }
}

Set-Location $currentLocation

So there’s a few things going on, in the first part of the code we are setting up the path to the cmdlet we are looking to test and then “dot-sourcing” it into the current scope of our test script. This is also very importing for the mocking to work correctly as the Pester framework will need any functions present in scope to be able to mock them.

We then create a Describe block giving it a name of Add-ClientNameToDatabase, for the testing that I’ve done I’ve stuck with naming the describe blocks the same as my target test script.

I then create a Mock the Invoke-SqlCmd function, saying that anytime this function is called within the current scope return a value of 1.

A It block is then added which will then both invoke and assert a test. In this case I’ve also arranged some of the test by composing an array of settings (TargetDatabase and TargetServer). The call to the function Add-ClientNameToDatabasethen takes place with the necessary parameters. We then assert that the mock of Invoke-Sqlcmd has been called exactly once (within the cope of the It block).

Finally a call just to set the current location back to where it started is performed, this is a standard housekeeping call made just in case the calling function has changed the current location.

To run the test, we use the Invoke-Pester command, in my experience you need to be very conscious of where in the folder structure your current PowerShell session is currently sitting. The way that I’ve dot-sourced the private function in the test means that it will attempt to load the function from a child folder named “Private” from the current location. So to run this test successfully you need to be in a folder above “Private” (so probably in the root folder of your project).

Calling Invoke-Pester will then execute all tests that end with .tests in their file name, to execute a specific test the full path to the test (including the name) can be passed as a parameter to Invoke-Pester. A short summary of the test results is then displayed indicating the number of tests passed and which ones failed, similar to the one below:

In posts that follow I’m hoping to cover some of the more interesting challenges I’ve had with mocking functions, in particular functions that returns values from databases (result sets) and functions that return more complex structures (like from the Az module)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s