Sunday, March 10, 2019

Unit Testing PowerShell Modules with Pester

Pester is a unit testing framework for Powershell. There are some good tutorials for it on their github page, and a few other places, but I'd like to pull together some of the key motivating use cases I've found and a couple of the gotchas.

Let's start with a very simple example.

This is the contents of a simple utility module named Util.psm1

function Get-Sum([int]$number1, [int]$number2) {
    $result = $number1 + $number2;
    write-host "Result is: $($result)";
    return $result;
}

And this is the content of a simple unit test file named UtilTest.ps1
Import-Module .\Util.psm1
Describe "Util Function Tests" {
    It "Get-Sum Adds two numbers" {
        Get-Sum 2 2 | Should be 4;
    }
}

We can run these tests using "Invoke-Pester .\UtilTest.ps1".



And already there's a gotcha here that wasn't obvious to me from the examples online. Let's say I change my function to say "Sum is:" instead of "Result is" and save the file. When I re-run my pester tests I still see "Result is:" printed out.

What's also interesting is that the second run rook 122 ms, while the first took 407 ms.

It turns out both of these changes are results of the same fact - once the module you are testing is loaded into memory it will stay there until you Remove it. That means any changes you make trying to fix your unit tests won't take effect until you've refreshed the module. The fix is simple


Import-Module .\Util.psm1
Describe "Util Function Tests" {
    It "Get-Sum Adds two numbers" {
        Get-Sum 2 2 | Should be 4;
    }
}
Remove-Module Util;

Removing the module after running your tests makes powershell pull a fresh copy into memory so you can see the changes.

The next gotcha is using the Mock keyword. Let's say I want to hide the write-host output in my function so it doesn't clutter up my unit tests. The obvious way is to use the "Mock" keyword to create a new version of write-host that doesn't actually write anything. My first attempt looked like this


Import-Module .\Util.psm1
Describe "Util Function Tests" {
    It "Get-Sum Adds two numbers" {
        Mock write-host;
        Get-Sum 2 2 | Should be 4;
    }
}
Remove-Module Util;

But I still see the write-host output in my unit test results.


It turns out the reason is that the Mock keyword creates mock objects in the current scope, instead of in scope for the module being tested. There are two ways of fixing this. One is the InModuleScope, or the ModuleName parameter on the Mock object. Here's an example of the first option


Import-Module .\Util.psm1

InModuleScope Util {
    Describe "Util Function Tests" {
        It "Get-Sum Adds two numbers" {
            Mock write-host;
            Get-Sum 2 2 | Should be 4;
        }
    }
}
Remove-Module Util;

And just like that the output goes away!


1 comment: