This is a two-chapter example from my book The Pester Book. If want to learn how to test PowerShell code, the unit testing framework Pester is the de facto way to do it. This is the intro chapter. The Pester Book has over 200 pages of real-life, in depth examples covering every aspect of Pester and testing methodologies.

So you're ready to take the next step in PowerShell development by writing tests? That's great! To give you a peek at what you're in for in this book, let's go through what it takes to write tests with the PowerShell module, Pester.

Whether you're a professional software developer or weekend PowerShell scripter, Pester is the tool you need to test PowerShell code.

Installing Pester

Pester is preinstalled on Windows 10 and later, and on Windows Server 2016 or later. Pester currently works back to PowerShell v2. If you have the PowerShell Package Manager installed, you can use the Install-Module command to install Pester. If not, head to the project's GitHub page to download it as a ZIP file. When you extract the ZIP file, Pester will need to be in your C:\Program Files\WindowsPowerShell\Modules folder or any other folder in your'PSModulePath' environment variable. You can look at this environment variable by running $env:PSModulePath.

Also, when you download Pester, your browser might mark the downloaded ZIP file as potentially unsafe. If so, use the Unblock-File command to unblock the ZIP file before extracting it. You can also right-click the file, and select Properties from Windows Explorer to find an Unblock button.

Creating a PowerShell Pester Test

In your PowerShell console with Pester installed, you can quickly create a simple project. Pester provides a command called New-Fixture that scaffolds out a single PowerShell script and test file to work with. However, this command is now considered legacy and will be removed from future versions Pester. Don't worry, though, creating a simple test is straightforward.

To build a Pester test, you need, at a minimum, two files: a PS1 script which contains code to test and a test script file. Create a folder called Pester101 somewhere, a PS1 script called Pester101.ps1 and an associated test script file called Pester101.Tests.ps1. Below is some code to get that done for you.

PS> New-Item -Path 'C:\Pester101' -ItemType Directory
PS> New-Item -Path 'C:\Pester101\Install-Pester.ps1' -ItemType File
PS> New-Item -Path 'C:\Pester101\Install-Pester.Tests.ps1' -ItemType File

Inside of Install-Pester.ps1, create a single function (you'll learn Pester loves functions), called Install-Pester and leave it blank. The function name can be anything.

function Install-Pester { param() }
Install-Pester.ps1

Once you have the Install-Pester.ps1 script created, now add the following code to the Install-Pester.Tests.ps1 file at the top. The first three lines code aren't related to Pester, per se; it's merely a way to dot sourcing the function inside of Install-Pester.ps1 to make available inside of this test script. The describe block below that is where the magic happens.

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"

describe 'Install-Pester' {
    it 'does something useful' {
        $true | Should -Be $false
    }
}
Install-Pester.Tests.ps1

Congratulations! You've officially created your first Pester test!

Running the Test with PowerShell

Now that you have a basic test scaffold built out let's run the test as-is and see what happens. At this point, you haven't put any code into the Install-Pester function in the Install-Pester.ps1 file. The function does nothing.

If you're not already, change to the C:\Pester101 folder then run Invoke-Pester.

PS> cd C:\Pester101
PS> Invoke-Pester

When Invoke-Pester runs, it will automatically look for any files ending with Tests.ps1 in the same folder you're in or any subfolders. If it finds one, it runs it.

Failing Pester Test

Notice the [-] and red text. This indicator means that the test has failed. Below that indicator, it will tell you why. In this instance, it has failed because the boolean value $false was supposed to be $true which, as you probably know, is not valid. Unit tests in Pester are returning true or false.

Writing A Passing Pester Test

So you've failed but don't sweat, I'll help you pass this test. Open up the C:\Pester101\Install-Pester.ps1 file in your favorite editor and insert a single line Write-Output "Working!" as shown below.

function Install-Pester {
    param()
    Write-Output "Working!"
}

Save the file and now open the C:\Pester101\Install-Pester.Tests.ps1 file. Replace the entire describe section with the below code.

describe 'Install-Pester' {
    it 'outputs a string' {
        Install-Pester | Should -Be 'Working!'
    }
}

Now, run Invoke-Pester again while in the C:\Pester101 folder and see what happens.

PowerShell Pester Test Pass

Notice now that your test has passed indicated by the [+] and green text.

How this PowerShell Test Worked

You've now successfully gone through all there is to create and run tests in Pester, but how did that work anyway?

  1. You created two files; Install-Pester.ps1 and Install-Pester.Tests.ps1 in the C:\Pester101 directory.
  2. The Install-Pester.ps1 script represented the code to test. The Install-Pester.Tests.ps1 script held the tests to run against the code in Install-Pester.ps1.
  3. When Invoke-Pester was run while inside of the C:\Pester101 directory, it looked for all scripts ending in .Tests.ps1.
  4. Once Invoke-Pester found all of the scripts, it then ran each one (in this case, a single one).
  5. When the Install-Pester.Tests.ps1 test script was run, it dot sourced the Install-Pester.ps1 script so that Pester could run the Install-Pester function inside.
  6. Pester then executed that function inside of the test (doing nothing at all the first time) and then compared the boolean value $true with the boolean value $false. Since the test was expecting $true to be $false, it failed.
  7. On the second round, Pester dot sourced the Install-Pester function again, ran it and this time captured the output of Working!. It then compared that output to the expected output of Working!. Since they were both the same, the test passed.

Understanding Pester Blocks

Pester's domain-specific language (DSL) is heavily reliant on PowerShell scriptblocks. Scriptblocks allow Pester to segment code, control how it's run and more. Scriptblocks are what will enable Pester to do most of the unit-testing magic it does! To understand Pester, you must first understand the various kinds of scriptblocks (blocks) that Pester uses to execute code.

In Pester's DSL, you'll see six types of blocks:

  • Describe
  • Context
  • It
  • BeforeEach/BeforeAll
  • AfterEach/AfterAll
  • Mocks

Each of these blocks provides Pester the ability to control the order in which code is executed when a test runs, control scope to ensure one block's activities don't conflict with another or to allow a child scope to inherit the scope of its parent.

Describe Blocks

The highest level in your test file is a describe block. A describe block is a logical grouping of tests. Each test file can have one or more describe blocks. Nearly everything happens inside a describe block. If a test file contains one or more describe blocks, describe blocks are the containers for almost everything else.

Like all of the other blocks covered in this chapter, a describe block is a PowerShell scriptblock that contains all of the code necessary to carry out a Pester test.

The describe block, at its simplest, consists of a name and a scriptblock indicated by the opening and closing curly braces.

describe 'Stop-MailDaemon' {
    ## Stuff goes in here
}

Describe blocks also separate scope that, when performing unit tests is critical to understand.

Context Blocks

The next level down from a describe block is the context block. The context block resides inside of a describe block and provides a logical grouping of it blocks which you'll cover next. A context block, like a describe block has a name and a scriptblock definition that contains code inside of the context block. They are optional so, unlike the describe block, don't have to be used to build tests.

describe -Tag 'Linux' 'Stop-MailDaemon' {
    ## Describe-specific code in here
    context 'When the server is down' {
        ## Context-specific code in here
    }
}

Context blocks not only allow you to organize tests better but they also allow you to define separate scopes which you'll learn a lot more about in the Mocking Introduction chapter.

It Blocks

It blocks are the next step down from the optional context block. It blocks can be in a describe block or a context block. It blocks assert an expectation for your code. They contain the code that checks the actual state with the expected state. It blocks are what most people are referring to when they talk about a Pester test.

It blocks have a name and a scriptblock definition. The name of an it block is up to you but its best practice to give it a descriptive name of what state it's testing. Be sure to check out the Test Design Practices chapter for some guidance there.

For example, if you're testing whether the code throws an exception, a good it block name would be "throws an exception" or, if the test within checks to see if a script is executed, the name could be "the script runs." Keep your names simple and to the point.

describe -Tag 'Linux' 'Stop-MailDaemon' {
    it 'the script runs' {
        ## Code to compare the state to test with the real state
    }
    context 'When the server is down' {
        it 'throws an exception' {
            ## Code to compare the state to test with the real state
        }
    }
    }
}

An it block (test) can have one of five possible results:

  • [+] Passed: The test ran, and the expectation was met.
  • [-] Failed: The test ran and the expectation was not met.
  • [?] Inconclusive: The test ran but it did not pass nor did it fail.
  • [!] Skipped: The test was not run because it was put in a skipped state.
  • [?] Pending: The test was not run because it was empty or is pending.

We cover the skipped, pending and inconclusive states in depth in the Controlling Test Results chapter.

Before and After Blocks

By default, Pester runs code from top to bottom. However, there may be times when you need code run before any it blocks, right after or in between. In this case, you would use one or more BeforeAll, BeforeEach, AfterAll, or AfterEach blocks. These blocks run arbitrary code that executes at certain times during the test run.

These blocks can contain whatever arbitrary code you need. They may set up some logging, create a database connection, or whatever else you need. They can appear in a describe or a context block. These blocks are dot-sourced into the context, or describe in which they appear, meaning they can also do things like define variables, which would then be "visible" to any it blocks in the same describe or context. It doesn't matter where within the describe or context, these blocks appear, nor does it matter in which order they appear. However, for readability, it's customary to put BeforeAll/BeforeEach blocks at the top of the describe or context block, and to put AfterAll/AfterEach blocks at the bottom.

describe -Tag 'Linux' 'Stop-MailDaemon' {
    BeforeAll {
        ## Code in here
    }
    BeforeEach {
        ## Code in here
    }

    it 'the script runs' {
        ## Code to compare the state to test with the real state
    }

    context 'When the server is down' {
        it 'throws an exception' {
            ## Code to compare the state to test with the real state
        }
    }
    }
    AfterEach {
        ## Code in here
    }
    AfterAll {
        ## Code in here
    }
}

The names are self-explanatory.

  • BeforeAll runs before any it block runs.
  • AfterAll runs after all it blocks have run.
  • BeforeEach runs before each it block runs-meaning, if you have five it blocks, BeforeEach runs five times.
  • AfterEach runs after each it block runs.

If you define BeforeEach/AfterEach in a describe block and also within a child context block, the ones in the describe block run first, followed by the ones in the context block. For example:

describe -Tag 'Linux' 'Stop-MailDaemon' {
    BeforeEach {
        ## Runs first
    }

    it 'the script runs' {
        ## Code to compare the state to test with the real state
    }

    context 'When the server is down' {
            BeforeEach {
                ## Runs second
            }
        it 'throws an exception' {
            ## Runs third
        }
    }
    }
}

Have you ever used try/catch/finally blocks in PowerShell? This is what Pester uses under the covers. Pester places the Before* code before all others. Then, once the it block, or describe block has completed execution, Pester inserts code contained in the AfterEach or AfterAll blocks into a finally block for execution.

Summary

This two-chapter sample of The Pester Book is a great starting point for anyone wanting to learn about writing PowerShell tests. Building tests for your PowerShell scripts will definitely up your PowerShell game to a new level!

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!