Last week I posted a Getting Started with Pester Testing in PowerShell guide that went over how to install Pester, write your first test script, and run that script against a module. This week I’m going to dive more into the Should command and its operators, which is the basis for writing Pester assertions or tests. These are the heart of Pester and make up the tests for our code.

This post is using Pester version 5.0.2 and PowerShell version 7.0.1 running on Windows 10.

Performing Assertions with Should

Should is a command that performs our tests or assertions in our script. It is used for comparing objects and throwing failures when the test is expected to fail. We use Should inside of It blocks in the test script. Should also has different operators for performing tests.

Last week we wrote some basic assertions testing our module code in different scenarios: when we didn’t use a parameter and when we did. We used the -Be and -BeOfType operators for performing the tests. Let’s review these assertions:

It "should return 'I got something!'" {
    Get-Something | Should -Be 'I got something!'
}

It "should be a string" {
    Get-Something | Should -BeOfType System.String
}

$thing = 'a dog'

It "should return 'I got' follow by parameter value" {
    Get-Something -ThingToGet $thing | Should -Be "I got $thing!"
}

It "should be a string" {
    Get-Something -ThingToGet $thing | Should -BeOfType System.String
}

Running Get-Something without a parameter should return the default string “I got something!” while specifying a parameter should return “I got” followed by the string. In both cases, the output should be of System.String type.

In addition to say what something should be, we can also test for the negative and say what things should not be. You can perform this using the -Not operator. Keep an eye out for this operator in the upcoming examples.

Let’s jump into our other Should operators.

BeExactly, BeLike, BeLikeExactly

This will do a case sensitive comparison of two objects. This is like the Be operator but used when case sensitivity is important. If we wanted to make sure our output string “I got something!” always had the correct case, we can update the assertion like this:

It "should return 'I got something!'" {
    Get-Something | Should -BeExactly 'I got something!'
}

If someone updated our code to output “i got something!” with a lowercase i, then our Pester test would fail.

We can also use wildcards in our comparison along with specifying if it is case-sensitive or not with BeLike and BeLikeExactly. Note the differences in the capital “I” and lowercase “i”.

$string = "i got something!"
$string | Should -BeLike "I got *"  # Test will pass
$string | Should -BeLikeExactly "I got *"  # Test will fail

BeGreaterThan, BeGreaterOrEqual, BeLessThan, BeLessOrEqual

These operators will compare values for equality between objects. These use the common PowerShell operators for value comparison, like -gt, -ge, -lt, and -le.

For example, if you want to make sure the version is greater than a specific version:

[version]'2.0.1' | Should -BeGreaterThan ([version]'1.5.0')

Or just comparing number values:

10 | Should -BeGreaterOrEqual 10  # Test will pass
$Error.Count | Should -BeLessThan 1  # Hopefully this passes!
10 | Should -BeLessOrEqual 10  # Test will pass

BeTrue, BeFalse

Pretty straight forward, tests if the value should be true or false. While the Test-NetConnection may not be practical, I wanted to should something else besides passing a variable to the Should statement. Maybe I can find a more practical example in another post when I look at TestDrive and TestRegistry.

$false | Should -BeFalse
0 | Should -BeFalse
$true | Should -BeTrue
1 | Should -BeTrue

# Testing Network Connection
Test-NetConnection -ComputerName 192.168.1.1 -InformationLevel Quiet | Should -BeTrue

Contain

Checks that a collection contains the value. Combine with the -Not operator to check for collection not containing a value.

'apple', 'banana', 'carrot' | Should -Contain 'banana'  # Test will pass
'apple', 'banana', 'carrot' | Should -Not -Contain 'lemon'  # Test will pass
1..100 | Should -Contain 44  # Test will pass
1..100 | Should -Contain 888  # Test will fail

FileContentMatch, FileContentMatchExactly, FileContentMatchMultiple

Check to see if a file contains the specified text and whether or not it should be case sensitive (Match vs. MatchExactly). MatchMultiple will check for the content of the file being tested as one string object so you can compare across multiple lines.

Let’s say I have a file in C:\temp/myFile.txt with the following content:

This is my test string in my file.
This is the second line in my file.

I could write single or multi-line tests to match content in the file:

'C:\temp\myFile.txt' | Should -FileContentMatch 'my test string'  # Test will pass

'C:\temp\myFile.txt' | Should -FileContentMatch 'in my file.*\n.*This is the'  # Test will fail, not multiline test

'C:\temp\myFile.txt' | Should -FileContentMatchMultiline 'in my file.*\n.*This is the'  # Test will pass, testing multiline

Note that the file path being passed in the pipeline is in quotes, otherwise you’ll get an error stating Cannot run a document in the middle of a pipeline.

Testing with the multiline operator is tricky and I haven’t quite figured it out yet. At the end of this article, I wanted to include a test to make sure that my function has comment-based help but couldn’t quite get the syntax. If I can figure it out I’ll probably write a new article on it.

BeNullOrEmpty

Checks values for null or empty string. Like the other operators, this can be combined with -Not to indicate it should not be null or empty.

$null | Should -BeNullOrEmpty  # Test will pass
$null | Should -Not -BeNullOrEmpty

Because

Adds a message to the test failure message.

$myString = "this is my string"
$myString | Should -Be "this is my string" -Because "they are the same string"
$myString | Should -Be "not my string" -Because "the strings are not the same"

The second test will fail, and the message will appear in the test failure output:

Expected strings to be the same, because the strings are not the same, but they were different.
   Expected length: 13
   Actual length:   17
   Strings differ at index 0.
   Expected: 'not my string'
   But was:  'this is my string'
   at $myString | should -Be "not my string" -Because "the strings are not the same"

HaveParameter

This operator verifies parameters on functions and their associated properties, like making sure they are of the correct type and if they are mandatory. Use the Get-Command cmdlet to get the properties of the function.

# Verifies the command has a mandatory parameter named 'Name'
Get-Command Resolve-DnsName | Should -HaveParameter Name -Mandatory

# Example function
function Get-Something {
   [string]
   $MyString = "this is my string"
}

# Get-Something Tests, all should pass
Get-Command Get-Something | Should -HaveParameter MyString -Type String
Get-Command Get-Something | Should -HaveParameter MyString -DefaultValue "this is my string"
Get-Command Get-Something | Should -HaveParameter MyString -Not -Mandatory

Putting Operators into Action

Let’s take a look at some additional assertions I added to our Pester test script from last time. While I focused on making sure my command had the correct output, we can also use Pester to verify our module is properly formatted. Here’s the function again, slightly updated from last time:

function Get-Something {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $ThingToGet = "something"
    )

    Write-Output "I got $ThingToGet!"
}

For example, we can make sure that our Get-Something function has the required -ThingToGet parameter, that it is of type String and has a default value of “something”. Here is a new Context block with those three assertions written into a single It block:

Context "testing parameter ThingToGet" {
  It "should have a parameter named ThingToGet" {
    Get-Command Get-Something | Should -HaveParameter ThingToGet -Type String
    Get-Command Get-Something | Should -HaveParameter ThingToGet -DefaultValue "something"
    Get-Command Get-Something | Should -HaveParameter ThingToGet -Not -Mandatory
  }
}

We can also write some assertions to make sure that the module has the Get-Something function defined in it. Within a Context or It block, we can run other commands to gather information prior to running the tests. In this case, we’ve already imported the module into the current session, so we can run Get-Module to get information about it, including exported functions.

Context "verify module has defined functions" {
  $moduleFunctions = (Get-Module PSSSomethingModule).ExportedFunctions.Keys
  
  it "should have a Get-Something function" {
    $moduleFunctions | Should -Contain 'Get-Something'
  }
}

The $moduleFunctions will have all the exported functions, and we can use the -Contains operator to verify the Get-Something value is in it.

Here’s the updated Pester test script for the module:

Import-Module .\PSSomethingModule.psm1 -Force

Describe "Get-Something" {
    Context "verify module has defined functions" {
        $moduleFunctions = (Get-Module PSSSomethingModule).ExportedFunctions.Keys
        
        it "should have a Get-Something function" {
            $moduleFunctions | Should -Contain 'Get-Something'
        }
    }
    
    Context "testing parameter ThingToGet" {
        It "should have a parameter named ThingToGet" {
            Get-Command Get-Something | Should -HaveParameter ThingToGet -Type String
            Get-Command Get-Something | Should -HaveParameter ThingToGet -DefaultValue "something"
            Get-Command Get-Something | Should -HaveParameter ThingToGet -Not -Mandatory
        }
    }    
    
    Context "when parameter ThingToGet is not used" {
        It "should return 'I got something!'" {
            Get-Something | Should -Be 'I got something!'
        }

        It "should be a string" {
            Get-Something | Should -BeOfType System.String
        }
    }

    Context "when parameter ThingToGet is used" {
        $thing = 'a dog'
        
        It "should return 'I got' follow by parameter value" {
            Get-Something -ThingToGet $thing | Should -Be "I got $thing!"
        }

        It "should be a string" {
            Get-Something -ThingToGet $thing | Should -BeOfType System.String
        }
    }
}

This is what I meant by figuring out all the cool ways to test your code. It’s taking me a bit to figure out the different ways to test code, and the best thing is to look at other people’s examples. As the module gets more complicated, the more tests we can make.

Here’s our new output from running the updated test script:

Invoke-Pester -Output Detailed
PowerShell output of our updated Pester test script
PowerShell output of our updated Pester test script

Check out the code for the updated module and test script in my GitHub repo:
JeffBrownTech | pester-examples | 02-should-operators

Additional Reading:
Pester Docs: Performing Assertions with Should

Questions or comments? If so, drop me a note below or find me on Twitter or LinkedIn to discuss further.

Leave a Reply