DEV Community

Cover image for Test your PowerShell code with Pester
Chris Noring for Microsoft Azure

Posted on • Updated on

Test your PowerShell code with Pester

TLDR; this article covers the testing framework Pester that you use to test your PowerShell scripts.

Why test

The reason you want to have tests are many:

  • Correctness. Ensure your code works as as intended for certain scenarios.
  • Confidence. When you have a lot of tests covering your code it creates a level of confidence. With this confidence you start daring to change this, if you for example would need to refactor code and ensure it still works after those changes.
  • Architecture. Another reason for having tests is that it drives architecture. If you create tests around what you do, you ensure you build your code in a way that makes it testable. That, drives the architecture.

There are many other reasons for wanting to have tests but the three above are quite compelling.

What is Pester

Pester is a test framework meant for PowerShell and is a module you can install. It has several features:

  • Assertions. Pester comes with diverse ways of asserting conditions that will determine if your tests should fail or not.
  • Able to run tests. You can run tests with Pester, both a single test with a single piece of input as well as testing many different inputs at once.
  • Can group your tests in test suites. When you start having quite a few tests, you want a way to group those tests into larger logical groups, that's what test suites are.
  • Ability to mock calls. In you tests you might have calls to commands that carry out side-effects, like accessing a data store or creating a file for example. When you want your tests to focus on the behavior on the tests, mocking is a good idea.

Install

To install Pester, you run the below command.

Install-Module -Name Pester -Force
Enter fullscreen mode Exit fullscreen mode

Once it's installed, you can start authoring your tests.

From install page:

Pester runs on Windows, Linux, MacOS and anywhere else thanks to PowerShell. It is compatible with Windows PowerShell 3, 4, 5, 6 and 7.
Pester 3 comes pre-installed with Windows 10

Our first test

For our first test, we will learn how to write a test as well as running it.

  1. To create our first test, create a file A-Test.ps1
  2. Add the following code:
   Describe "A suite" {
      It "my first test" {
        $Value = "Value"
        $Value | Should -Be "Value"
      }
    }
Enter fullscreen mode Exit fullscreen mode

The test above, have a Describe construct which is the declaration of a suite, and a string argument, giving the suite a name. Within the suite there's a test definition It, which also has a string argument that represents the name of the test. Within the test, there's test itself where the code is set up:

   $Value = "Value"
Enter fullscreen mode Exit fullscreen mode

and then it's asserted upon:

   $Value | Should -Be "Value"
Enter fullscreen mode Exit fullscreen mode

Note the use of the Should -Be, which determines equality between $Value and "Value".

  1. To run the test, call Invoke-Pester (./ for the path in Linux ans macOS and .\ for Windows):
   Invoke-Pester ./A-Test.ps1 
Enter fullscreen mode Exit fullscreen mode

The outcome of running the test is:

   Starting discovery in 1 files.
   Discovery found 1 tests in 6ms.
   Running tests.
   [+] /<path>/A-Test.ps1 42ms (11ms|26ms)
   Tests completed in 44ms
   Tests Passed: 1, Failed: 0, Skipped: 0 NotRun: 0
Enter fullscreen mode Exit fullscreen mode

A more real looking test

The first test was great in that it let us understand the mechanics of testing and concepts like a suite, a test, and an assertion like Should -Be. A more real looking test would test code in a script file that's not in our test file. Here's the steps we will take next:

  • Create a script file with our production code.
  • Dot source said script file.
  • Create a test and run it.

Create production code

You will have code that you want to test but let's create this script file for the sake of demonstration.

  1. Create a file Get-Tomato.ps1
  2. Add the following code:
   Function Get-Tomato() {
     new-object psobject -property @{ Name = "Tomato" }
   }
Enter fullscreen mode Exit fullscreen mode

This code will create a custom object for each time the Get-Tomato() function is invoked.

  1. Let's dot source it next so are session knows about it:
   . ./Get-Tomato.ps1
Enter fullscreen mode Exit fullscreen mode
  1. Verify that your function has been picked up by running:
   Get-Tomato
Enter fullscreen mode Exit fullscreen mode

you should see the following in the console:

   Name                                                                                                                 ----                                                                                                                  
   Tomato
Enter fullscreen mode Exit fullscreen mode

Create the test

Now that we have our production code, let's author the test next.

  1. Create a file Get-Tomato.Tests.ps1 and give it the following code:
   Describe "Tomatoes" {
      It "Get Tomato" {
        $tomato = Get-Tomato
        $tomato.Name | Should -Be "Tomato"
      }
    }
Enter fullscreen mode Exit fullscreen mode
  1. Run the test with Invoke-Pester:
   Invoke-Pester ./Get-Tomato.Tests.ps1
Enter fullscreen mode Exit fullscreen mode

you should see the following output:

   Invoke-Pester ./Get-Tomato.Tests.ps1
   Starting discovery in 1 files.                                                                                                  Discovery found 1 tests in 6ms.                                                                                                 
   Running tests.
   [+] /<path>/Get-Tomato.Tests.ps1 66ms (9ms|52ms)
   Tests completed in 68ms
   Tests Passed: 1, Failed: 0, Skipped: 0 NotRun: 0
Enter fullscreen mode Exit fullscreen mode

Great, you ran a test on a more real looking code.

Working with side effects

You will have code that you write that eventually performs side effects, like accessing a network resource or create a file. Let's look at such a case and how Pester handles it. The short answer is that you can use mocks, a construct that's executed instead of the actual command. All you need to do is to focus that the right behavior happens.

Update production code

So, in this case, imagine that our production code now will have more function Save-Tomato that saves an object to a file.

  1. Update the _Get-Tomato.ps1_file with this code:
   Function Save-Tomato() {
      Param(
        [string] $Name
      )
      New-Item -ItemType File -Path ./Tomato.txt -Value $Name -Force
   }
Enter fullscreen mode Exit fullscreen mode
  1. Dot source the code to ensure it's being picked up:
   . /Get-Tomato.ps1
Enter fullscreen mode Exit fullscreen mode

Create a test

Ok, so you have added Save-Tomato() to your script file. Now for a test. Your code is calling New-Item, which creates a new file. As part of testing, you don't want it to create a file each time the test is being run. More likely, you just want to see the test does what it's supposed to, i.e. calling the correct command/s. So, to solve this issue, we can mock, replace the current implementation of New-Item with our own.

  • To mock, we first need to replace the actual implementation like so:

    Mock -CommandName New-Item -MockWith {}
    
  • Call the command. At this point, you need to call the command like you would usually do. In your case, it means that you call Save-Tomato():

   Save-Tomato # this should call New-Item
Enter fullscreen mode Exit fullscreen mode
  • Verify. The last part of the mocking process is to verify that you mock has been called
  1. The command Should -Invoke, allows you to specify what command it should have called, like so:
   Should -Invoke -CommandName New-Item -Times 1 -Exactly
Enter fullscreen mode Exit fullscreen mode

The above code verifies New-Item is called exactly one time.

  1. Let's put it all together as a test:
   It "Save tomato" {
      Mock -CommandName New-Item -MockWith {}
      Save-Tomato "my tomato"
      Should -Invoke -CommandName New-Item -Times 1 -Exactly
   }
Enter fullscreen mode Exit fullscreen mode
  1. Remove Tomato.txt and then run the test with Invoke-Pester like so:
   Invoke-Pester ./Get-Tomato.Tests.ps1
Enter fullscreen mode Exit fullscreen mode

At this point your tests should run successfully like so:

   Starting discovery in 1 files.
   Discovery found 2 tests in 8ms.
   Running tests.
   [+] /Users/chnoring/Documents/dev/projects/powershell-projects/articles/Get-Tomato.Tests.ps1 78ms (34ms|37ms)
   Tests completed in 80ms
   Tests Passed: 2, Failed: 0, Skipped: 0 NotRun: 0
Enter fullscreen mode Exit fullscreen mode

Also, note how Tomato.txt isn't created, because you are mocking the call to New-Item, success.

Congrats, you've learned how to install test framework Pester, on top of that, you've learned to author your first tests and even learned how to mock the call to actual commands. To learn more, have a look at Pesters GitHub page:

Pester GH page

Discussion (0)