PSST! Unit Testing with the PowerShell Suite for Testing

PowerShell is a very powerful language and framework. It’s not just for “advanced batch files”. When programming in PowerShell, you are encouraged to write modular code that can be combined in different ways. Your PowerShell code deserves the same quality testing that other languages provide.

If you look at most unit testing frameworks, they all provide a core set of features for writing test cases:

  • A way to describe correct behavior. Typically this is done through a set of assertions. A test script is written with the “3 As”: Arrange, Act, and Assert. A good assertion library makes the intent of the test easy to code and understand.
  • A way to run one or more tests. A test runner will locate and run tests, and report on the success or failure of the tests.
  • A way to mock parts of the system that are not part of the system under test.

Depending on the framework you choose, you may need to combine two or more frameworks to get all of the pieces that you need.

PSST contains three tools to let you write your unit tests for PowerShell:

  • PShould – a fluent assertion module
  • PSMock – a mocking module
  • PSate – a test runner module

Let’s see how we can start writing PowerShell tests with PSST. Assume we have a calculator class that we want to test:

function Add-Numbers($a, $b) {
   
    if ($a -eq $null) { throw '$a cannot be null' }
    if ($b -eq $null) { throw '$b cannot be null' }

    return $a + $b
}

The first thing you would do is to write some assertions. Let’s import PShould and write a few cases:

Import-Module PShould
Add-Numbers 1 2 | Should Be 3
Add-Numbers 0 0 | Should Be 0

PShould supports lots of tests like “Should Contain” “Should Exist” and “Should Throw”. Let’s check for some throws:

{ Add-Numbers $null 1 } | Should Throw "cannot be null"
{ Add-Numbers 1 } | Should Throw "cannot be null"

Great. Now we have some tests that check the behavior of our calculator. But what if our code does something more complicated, such as opening a file or accepting input? Let’s try that with a calculator that takes input:

function Get-Number {
    param ([int] $which)

    Write-Host "Enter number ${which}:"
    [int] $i = Read-Host
    $i
}

function Do-Calculator {
    return Add-Numbers (Get-Number 1) (Get-Number 2)
}

Do-Calculator

We want to test Do-Calculator, but it takes inputs from the user, so we can’t run an automated test case on it without doing some more work. Here we want to mock the Get-Number method so that we control the results. We will use PSMock to mock the method.

Import-Module PSMock
Enable-Mock | iex
Mock Get-Number { 1 }
Do-Calculator | Should Be 2

PSMock automatically creates a function that matches the one you are mocking, and lets you quickly override the function. It even lets you do conditional mocks:

Mock Get-Number { 1 } -when { $which -eq 1 }
Mock Get-Number { 2 } -when { $which -eq 2 }
Do-Calculator | Should Be 3

You could probably do this simply by writing your own functions, but PSMock lets you dynamically add and remove mocks, restoring the original methods. PSMock also lets you mock commandlets such as Get-ChildItem.

MockContext {
    Mock Get-ChildItem { "nothing" }
    Get-ChildItem c: | Should Be "nothing"
}

PSMock also provides a MockContext function, which automatically cleans up any outstanding mocks. This is very important if you are mocking any system commandlets (or you won’t be able to do a dir anymore!)

The next step is to organize our test cases with PSate:

Describing "Calculator" {
    Given "two numbers" {
        TestSetup {
            Mock Get-Number { 1 } -when { $which -eq 1 }
            Mock Get-Number { 2 } -when { $which -eq 2 }
        }

        It "adds them" {
            Do-Calculator | Should Be 3
        }
    }

    Given "a null number" {
        TestSetup {
            Mock Get-Number { $null } -when { $which -eq 1 }
            Mock Get-Number { 2 } -when { $which -eq 2 }
        }

        It "throws" {
            { Do-Calculator } | Should Throw
        }
    }
}

Here we have organized our tests. The Describe block tells us what we are testing. The Given block describes a test scenario, which the TestSetup sets up. The It block asserts our tests for us. PSate even gives us pretty outputs:

Describing Calculator
  Given two numbers
    [+] It adds them [3 ms]
  Given a null number
    [+] It throws [3 ms]

We can even integrate PSate into our automated build process with Jenkins, TFS, or other tools.

Get started with PSST by visiting the project pages for documentation and installation instructions:

    • PShould – a fluent assertion module
    • PSMock – a mocking module
    • PSate – a test runner module

3 thoughts on “PSST! Unit Testing with the PowerShell Suite for Testing”

  1. Jon, is there a way to mock .NET classes / functions? I have come across a number of use cases where I have built cmdlets, the internals of which may use .NET classes / functions. Ideally I’d like to mock those in order to test the rest of the cmdlet. A good example is something that wraps a database access library – we’d want to mock the underlying .NET DB calls.

    1. I can think of two ways. 1. Wrap the .net calls in functions, then use PSmock on those. 2. Use a mock library. I happen to like Moq, which can mock any interface or virtual method.

      1. I was naturally going to go for option 1. as it seemed like the only option – however that didn’t feel right. I’d be building Cmdlets / functions that are essentially factories purely for the purpose of allowing me to mock them – a little too like the “tail wagging the dog” for my liking. I think it’s worthwhile making a note in the documentation of PSMock to address this use case. I’ll take a look at Moq – thanks for the tip.

Leave a Reply